blob: 8d9e8557130b5ec46bf4f12cd0771ef207482056 [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
33
Tao Baof3282b42015-04-01 11:21:55 -070034from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080035
Doug Zongkereef39442009-04-02 12:14:19 -070036
Dan Albert8b72aef2015-03-23 19:13:21 -070037class Options(object):
38 def __init__(self):
39 platform_search_path = {
40 "linux2": "out/host/linux-x86",
41 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070042 }
Doug Zongker85448772014-09-09 14:59:20 -070043
Dan Albert8b72aef2015-03-23 19:13:21 -070044 self.search_path = platform_search_path.get(sys.platform, None)
45 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080046 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070047 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
49 self.java_args = "-Xmx2048m" # JVM Args
50 self.public_key_suffix = ".x509.pem"
51 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070054 self.boot_signer_args = []
55 self.verity_signer_path = None
56 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070057 self.verbose = False
58 self.tempfiles = []
59 self.device_specific = None
60 self.extras = {}
61 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070062 self.source_info_dict = None
63 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070064 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070065 # Stash size cannot exceed cache_size * threshold.
66 self.cache_size = None
67 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070068
69
70OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070071
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080072
73# Values for "certificate" in apkcerts that mean special things.
74SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
75
76
Dan Albert8b72aef2015-03-23 19:13:21 -070077class ExternalError(RuntimeError):
78 pass
Doug Zongkereef39442009-04-02 12:14:19 -070079
80
81def Run(args, **kwargs):
82 """Create and return a subprocess.Popen object, printing the command
83 line on the terminal if -v was specified."""
84 if OPTIONS.verbose:
85 print " running: ", " ".join(args)
86 return subprocess.Popen(args, **kwargs)
87
88
Ying Wang7e6d4e42010-12-13 16:25:36 -080089def CloseInheritedPipes():
90 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
91 before doing other work."""
92 if platform.system() != "Darwin":
93 return
94 for d in range(3, 1025):
95 try:
96 stat = os.fstat(d)
97 if stat is not None:
98 pipebit = stat[0] & 0x1000
99 if pipebit != 0:
100 os.close(d)
101 except OSError:
102 pass
103
104
Tao Bao2c15d9e2015-07-09 11:51:16 -0700105def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700106 """Read and parse the META/misc_info.txt key/value pairs from the
107 input target files and return a dict."""
108
Doug Zongkerc9253822014-02-04 12:17:58 -0800109 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700110 if isinstance(input_file, zipfile.ZipFile):
111 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800112 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700113 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800114 try:
115 with open(path) as f:
116 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700117 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800118 if e.errno == errno.ENOENT:
119 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700120 d = {}
121 try:
Michael Runge6e836112014-04-15 17:40:21 -0700122 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700123 except KeyError:
124 # ok if misc_info.txt doesn't exist
125 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700126
Doug Zongker37974732010-09-16 17:44:38 -0700127 # backwards compatibility: These values used to be in their own
128 # files. Look for them, in case we're processing an old
129 # target_files zip.
130
Doug Zongker37974732010-09-16 17:44:38 -0700131 if "recovery_api_version" not in d:
132 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700133 d["recovery_api_version"] = read_helper(
134 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700135 except KeyError:
136 raise ValueError("can't find recovery API version in input target-files")
137
138 if "tool_extensions" not in d:
139 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800140 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700141 except KeyError:
142 # ok if extensions don't exist
143 pass
144
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800145 if "fstab_version" not in d:
146 d["fstab_version"] = "1"
147
Tao Bao84e75682015-07-19 02:38:53 -0700148 # A few properties are stored as links to the files in the out/ directory.
149 # It works fine with the build system. However, they are no longer available
150 # when (re)generating from target_files zip. If input_dir is not None, we
151 # are doing repacking. Redirect those properties to the actual files in the
152 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700153 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400154 # We carry a copy of file_contexts.bin under META/. If not available,
155 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700156 # to build images than the one running on device, such as when enabling
157 # system_root_image. In that case, we must have the one for image
158 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700159 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
160 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700161 if d.get("system_root_image") == "true":
162 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700163 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700164 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700165 if not os.path.exists(fc_config):
166 fc_config = None
167
168 if fc_config:
169 d["selinux_fc"] = fc_config
170
Tao Bao84e75682015-07-19 02:38:53 -0700171 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
172 if d.get("system_root_image") == "true":
173 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
174 d["ramdisk_fs_config"] = os.path.join(
175 input_dir, "META", "root_filesystem_config.txt")
176
Doug Zongker37974732010-09-16 17:44:38 -0700177 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800178 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700179 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700180 if not line:
181 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700182 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700183 if not value:
184 continue
Doug Zongker37974732010-09-16 17:44:38 -0700185 if name == "blocksize":
186 d[name] = value
187 else:
188 d[name + "_size"] = value
189 except KeyError:
190 pass
191
192 def makeint(key):
193 if key in d:
194 d[key] = int(d[key], 0)
195
196 makeint("recovery_api_version")
197 makeint("blocksize")
198 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700199 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700200 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700201 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700202 makeint("recovery_size")
203 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800204 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700205
Tao Bao48550cc2015-11-19 17:05:46 -0800206 if d.get("no_recovery", False) == "true":
207 d["fstab"] = None
208 else:
209 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
210 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800211 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700212 return d
213
Doug Zongkerc9253822014-02-04 12:17:58 -0800214def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700215 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800216 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700217 except KeyError:
218 print "Warning: could not find SYSTEM/build.prop in %s" % zip
219 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700220 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700221
Michael Runge6e836112014-04-15 17:40:21 -0700222def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700223 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700224 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700225 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700226 if not line or line.startswith("#"):
227 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700228 if "=" in line:
229 name, value = line.split("=", 1)
230 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700231 return d
232
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700233def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700234 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700235 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700236 self.mount_point = mount_point
237 self.fs_type = fs_type
238 self.device = device
239 self.length = length
240 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700241 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700242
243 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800244 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700245 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800246 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700247 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700248
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800249 if fstab_version == 1:
250 d = {}
251 for line in data.split("\n"):
252 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700253 if not line or line.startswith("#"):
254 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800255 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700256 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800257 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800258 options = None
259 if len(pieces) >= 4:
260 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700261 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800262 if len(pieces) >= 5:
263 options = pieces[4]
264 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700265 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800266 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800267 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700268 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700269
Dan Albert8b72aef2015-03-23 19:13:21 -0700270 mount_point = pieces[0]
271 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800272 if options:
273 options = options.split(",")
274 for i in options:
275 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700276 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800277 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700278 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800279
Dan Albert8b72aef2015-03-23 19:13:21 -0700280 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
281 device=pieces[2], length=length,
282 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800283
284 elif fstab_version == 2:
285 d = {}
286 for line in data.split("\n"):
287 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700288 if not line or line.startswith("#"):
289 continue
Tao Bao548eb762015-06-10 12:32:41 -0700290 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800291 pieces = line.split()
292 if len(pieces) != 5:
293 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
294
295 # Ignore entries that are managed by vold
296 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700297 if "voldmanaged=" in options:
298 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800299
300 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700301 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800302 options = options.split(",")
303 for i in options:
304 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700305 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800306 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800307 # Ignore all unknown options in the unified fstab
308 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800309
Tao Bao548eb762015-06-10 12:32:41 -0700310 mount_flags = pieces[3]
311 # Honor the SELinux context if present.
312 context = None
313 for i in mount_flags.split(","):
314 if i.startswith("context="):
315 context = i
316
Dan Albert8b72aef2015-03-23 19:13:21 -0700317 mount_point = pieces[1]
318 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700319 device=pieces[0], length=length,
320 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800321
322 else:
323 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
324
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700325 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700326 # system. Other areas assume system is always at "/system" so point /system
327 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700328 if system_root_image:
329 assert not d.has_key("/system") and d.has_key("/")
330 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700331 return d
332
333
Doug Zongker37974732010-09-16 17:44:38 -0700334def DumpInfoDict(d):
335 for k, v in sorted(d.items()):
336 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700337
Dan Albert8b72aef2015-03-23 19:13:21 -0700338
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700339def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
340 has_ramdisk=False):
341 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700342
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700343 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
344 'sourcedir'), and turn them into a boot image. Return the image data, or
345 None if sourcedir does not appear to contains files for building the
346 requested image."""
347
348 def make_ramdisk():
349 ramdisk_img = tempfile.NamedTemporaryFile()
350
351 if os.access(fs_config_file, os.F_OK):
352 cmd = ["mkbootfs", "-f", fs_config_file,
353 os.path.join(sourcedir, "RAMDISK")]
354 else:
355 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
356 p1 = Run(cmd, stdout=subprocess.PIPE)
357 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
358
359 p2.wait()
360 p1.wait()
361 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
362 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
363
364 return ramdisk_img
365
366 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
367 return None
368
369 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700370 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700371
Doug Zongkerd5131602012-08-02 14:46:42 -0700372 if info_dict is None:
373 info_dict = OPTIONS.info_dict
374
Doug Zongkereef39442009-04-02 12:14:19 -0700375 img = tempfile.NamedTemporaryFile()
376
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700377 if has_ramdisk:
378 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700379
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800380 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
381 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
382
383 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700384
Benoit Fradina45a8682014-07-14 21:00:43 +0200385 fn = os.path.join(sourcedir, "second")
386 if os.access(fn, os.F_OK):
387 cmd.append("--second")
388 cmd.append(fn)
389
Doug Zongker171f1cd2009-06-15 22:36:37 -0700390 fn = os.path.join(sourcedir, "cmdline")
391 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700392 cmd.append("--cmdline")
393 cmd.append(open(fn).read().rstrip("\n"))
394
395 fn = os.path.join(sourcedir, "base")
396 if os.access(fn, os.F_OK):
397 cmd.append("--base")
398 cmd.append(open(fn).read().rstrip("\n"))
399
Ying Wang4de6b5b2010-08-25 14:29:34 -0700400 fn = os.path.join(sourcedir, "pagesize")
401 if os.access(fn, os.F_OK):
402 cmd.append("--pagesize")
403 cmd.append(open(fn).read().rstrip("\n"))
404
Doug Zongkerd5131602012-08-02 14:46:42 -0700405 args = info_dict.get("mkbootimg_args", None)
406 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700407 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700408
Sami Tolvanen3303d902016-03-15 16:49:30 +0000409 args = info_dict.get("mkbootimg_version_args", None)
410 if args and args.strip():
411 cmd.extend(shlex.split(args))
412
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700413 if has_ramdisk:
414 cmd.extend(["--ramdisk", ramdisk_img.name])
415
Tao Baod95e9fd2015-03-29 23:07:41 -0700416 img_unsigned = None
417 if info_dict.get("vboot", None):
418 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700419 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700420 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700421 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700422
423 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700424 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700425 assert p.returncode == 0, "mkbootimg of %s image failed" % (
426 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700427
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100428 if (info_dict.get("boot_signer", None) == "true" and
429 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700430 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700431 cmd = [OPTIONS.boot_signer_path]
432 cmd.extend(OPTIONS.boot_signer_args)
433 cmd.extend([path, img.name,
434 info_dict["verity_key"] + ".pk8",
435 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700436 p = Run(cmd, stdout=subprocess.PIPE)
437 p.communicate()
438 assert p.returncode == 0, "boot_signer of %s image failed" % path
439
Tao Baod95e9fd2015-03-29 23:07:41 -0700440 # Sign the image if vboot is non-empty.
441 elif info_dict.get("vboot", None):
442 path = "/" + os.path.basename(sourcedir).lower()
443 img_keyblock = tempfile.NamedTemporaryFile()
444 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
445 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700446 info_dict["vboot_key"] + ".vbprivk",
447 info_dict["vboot_subkey"] + ".vbprivk",
448 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700449 img.name]
450 p = Run(cmd, stdout=subprocess.PIPE)
451 p.communicate()
452 assert p.returncode == 0, "vboot_signer of %s image failed" % path
453
Tao Baof3282b42015-04-01 11:21:55 -0700454 # Clean up the temp files.
455 img_unsigned.close()
456 img_keyblock.close()
457
Doug Zongkereef39442009-04-02 12:14:19 -0700458 img.seek(os.SEEK_SET, 0)
459 data = img.read()
460
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700461 if has_ramdisk:
462 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700463 img.close()
464
465 return data
466
467
David Zeuthend995f4b2016-01-29 16:59:17 -0500468def _BuildBvbBootableImage(sourcedir, fs_config_file, system_img_path,
469 info_dict=None, has_ramdisk=False):
470 """Build a bootable image compatible with Brillo Verified Boot from the
471 specified sourcedir.
472
473 Take a kernel, cmdline, system image path, and optionally a ramdisk
474 directory from the input (in 'sourcedir'), and turn them into a boot
475 image. Return the image data, or None if sourcedir does not appear
476 to contains files for building the requested image.
477 """
478
479 def make_ramdisk():
480 ramdisk_img = tempfile.NamedTemporaryFile()
481
482 if os.access(fs_config_file, os.F_OK):
483 cmd = ["mkbootfs", "-f", fs_config_file,
484 os.path.join(sourcedir, "RAMDISK")]
485 else:
486 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
487 p1 = Run(cmd, stdout=subprocess.PIPE)
488 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
489
490 p2.wait()
491 p1.wait()
492 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
493 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
494
495 return ramdisk_img
496
497 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
498 return None
499
500 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
501 return None
502
503 if info_dict is None:
504 info_dict = OPTIONS.info_dict
505
506 img = tempfile.NamedTemporaryFile()
507
508 if has_ramdisk:
509 ramdisk_img = make_ramdisk()
510
511 # use BVBTOOL from environ, or "bvbtool" if empty or not set
512 bvbtool = os.getenv('BVBTOOL') or "bvbtool"
513
514 # First, create boot.img.
515 cmd = [bvbtool, "make_boot_image"]
516
517 fn = os.path.join(sourcedir, "cmdline")
518 if os.access(fn, os.F_OK):
519 cmd.append("--kernel_cmdline")
520 cmd.append(open(fn).read().rstrip("\n"))
521
522 cmd.extend(["--kernel", os.path.join(sourcedir, "kernel")])
523
524 if has_ramdisk:
525 cmd.extend(["--initrd", ramdisk_img.name])
526
527 cmd.extend(["--rootfs_with_hashes", system_img_path])
528
529 args = info_dict.get("board_bvb_make_boot_image_args", None)
530 if args and args.strip():
531 cmd.extend(shlex.split(args))
532
533 rollback_index = info_dict.get("board_bvb_rollback_index", None)
534 if rollback_index and rollback_index.strip():
535 cmd.extend(["--rollback_index", rollback_index.strip()])
536
537 cmd.extend(["--output", img.name])
538
539 p = Run(cmd, stdout=subprocess.PIPE)
540 p.communicate()
541 assert p.returncode == 0, "bvbtool make_boot_image of %s image failed" % (
542 os.path.basename(sourcedir),)
543
544 # Then, sign boot.img.
545 cmd = [bvbtool, "sign_boot_image", "--image", img.name]
546
547 algorithm = info_dict.get("board_bvb_algorithm", None)
548 key_path = info_dict.get("board_bvb_key_path", None)
549 if algorithm and algorithm.strip() and key_path and key_path.strip():
550 cmd.extend(["--algorithm", algorithm, "--key", key_path])
551 else:
552 cmd.extend(["--algorithm", "SHA256_RSA4096"])
Ethan Xia37b4a982016-06-27 17:19:01 +0800553 cmd.extend(["--key", "external/bvb/test/testkey_rsa4096.pem"])
David Zeuthend995f4b2016-01-29 16:59:17 -0500554
555 args = info_dict.get("board_bvb_sign_boot_image_args", None)
556 if args and args.strip():
557 cmd.extend(shlex.split(args))
558
559 p = Run(cmd, stdout=subprocess.PIPE)
560 p.communicate()
561 assert p.returncode == 0, "bvbtool sign_boot_image of %s image failed" % (
562 os.path.basename(sourcedir),)
563
564 img.seek(os.SEEK_SET, 0)
565 data = img.read()
566
567 if has_ramdisk:
568 ramdisk_img.close()
569 img.close()
570
571 return data
572
573
Doug Zongkerd5131602012-08-02 14:46:42 -0700574def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
David Zeuthend995f4b2016-01-29 16:59:17 -0500575 info_dict=None, system_img_path=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700576 """Return a File object with the desired bootable image.
577
578 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
579 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
580 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700581
Doug Zongker55d93282011-01-25 17:03:34 -0800582 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
583 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700584 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800585 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700586
587 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
588 if os.path.exists(prebuilt_path):
589 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
590 return File.FromLocalFile(name, prebuilt_path)
591
592 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700593
594 if info_dict is None:
595 info_dict = OPTIONS.info_dict
596
597 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800598 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
599 # for recovery.
600 has_ramdisk = (info_dict.get("system_root_image") != "true" or
601 prebuilt_name != "boot.img" or
602 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700603
Doug Zongker6f1d0312014-08-22 08:07:12 -0700604 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthend995f4b2016-01-29 16:59:17 -0500605 if info_dict.get("board_bvb_enable", None) == "true":
606 data = _BuildBvbBootableImage(os.path.join(unpack_dir, tree_subdir),
607 os.path.join(unpack_dir, fs_config),
608 system_img_path, info_dict, has_ramdisk)
609 else:
610 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
611 os.path.join(unpack_dir, fs_config),
612 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700613 if data:
614 return File(name, data)
615 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800616
Doug Zongkereef39442009-04-02 12:14:19 -0700617
Doug Zongker75f17362009-12-08 13:46:44 -0800618def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800619 """Unzip the given archive into a temporary directory and return the name.
620
621 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
622 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
623
624 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
625 main file), open for reading.
626 """
Doug Zongkereef39442009-04-02 12:14:19 -0700627
628 tmp = tempfile.mkdtemp(prefix="targetfiles-")
629 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800630
631 def unzip_to_dir(filename, dirname):
632 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
633 if pattern is not None:
634 cmd.append(pattern)
635 p = Run(cmd, stdout=subprocess.PIPE)
636 p.communicate()
637 if p.returncode != 0:
638 raise ExternalError("failed to unzip input target-files \"%s\"" %
639 (filename,))
640
641 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
642 if m:
643 unzip_to_dir(m.group(1), tmp)
644 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
645 filename = m.group(1)
646 else:
647 unzip_to_dir(filename, tmp)
648
649 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700650
651
652def GetKeyPasswords(keylist):
653 """Given a list of keys, prompt the user to enter passwords for
654 those which require them. Return a {key: password} dict. password
655 will be None if the key has no password."""
656
Doug Zongker8ce7c252009-05-22 13:34:54 -0700657 no_passwords = []
658 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700659 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700660 devnull = open("/dev/null", "w+b")
661 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800662 # We don't need a password for things that aren't really keys.
663 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700664 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700665 continue
666
T.R. Fullhart37e10522013-03-18 10:31:26 -0700667 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700668 "-inform", "DER", "-nocrypt"],
669 stdin=devnull.fileno(),
670 stdout=devnull.fileno(),
671 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700672 p.communicate()
673 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700674 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700675 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700676 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700677 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
678 "-inform", "DER", "-passin", "pass:"],
679 stdin=devnull.fileno(),
680 stdout=devnull.fileno(),
681 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700682 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700683 if p.returncode == 0:
684 # Encrypted key with empty string as password.
685 key_passwords[k] = ''
686 elif stderr.startswith('Error decrypting key'):
687 # Definitely encrypted key.
688 # It would have said "Error reading key" if it didn't parse correctly.
689 need_passwords.append(k)
690 else:
691 # Potentially, a type of key that openssl doesn't understand.
692 # We'll let the routines in signapk.jar handle it.
693 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700694 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700695
T.R. Fullhart37e10522013-03-18 10:31:26 -0700696 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700697 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700698 return key_passwords
699
700
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800701def GetMinSdkVersion(apk_name):
702 """Get the minSdkVersion delared in the APK. This can be both a decimal number
703 (API Level) or a codename.
704 """
705
706 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
707 output, err = p.communicate()
708 if err:
709 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
710 % (p.returncode,))
711
712 for line in output.split("\n"):
713 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
714 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
715 if m:
716 return m.group(1)
717 raise ExternalError("No minSdkVersion returned by aapt")
718
719
720def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
721 """Get the minSdkVersion declared in the APK as a number (API Level). If
722 minSdkVersion is set to a codename, it is translated to a number using the
723 provided map.
724 """
725
726 version = GetMinSdkVersion(apk_name)
727 try:
728 return int(version)
729 except ValueError:
730 # Not a decimal number. Codename?
731 if version in codename_to_api_level_map:
732 return codename_to_api_level_map[version]
733 else:
734 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
735 % (version, codename_to_api_level_map))
736
737
738def SignFile(input_name, output_name, key, password, min_api_level=None,
739 codename_to_api_level_map=dict(),
740 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700741 """Sign the input_name zip/jar/apk, producing output_name. Use the
742 given key and password (the latter may be None if the key does not
743 have a password.
744
Doug Zongker951495f2009-08-14 12:44:19 -0700745 If whole_file is true, use the "-w" option to SignApk to embed a
746 signature that covers the whole file in the archive comment of the
747 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800748
749 min_api_level is the API Level (int) of the oldest platform this file may end
750 up on. If not specified for an APK, the API Level is obtained by interpreting
751 the minSdkVersion attribute of the APK's AndroidManifest.xml.
752
753 codename_to_api_level_map is needed to translate the codename which may be
754 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700755 """
Doug Zongker951495f2009-08-14 12:44:19 -0700756
Alex Klyubin9667b182015-12-10 13:38:50 -0800757 java_library_path = os.path.join(
758 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
759
760 cmd = [OPTIONS.java_path, OPTIONS.java_args,
761 "-Djava.library.path=" + java_library_path,
762 "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700763 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
764 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700765 if whole_file:
766 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800767
768 min_sdk_version = min_api_level
769 if min_sdk_version is None:
770 if not whole_file:
771 min_sdk_version = GetMinSdkVersionInt(
772 input_name, codename_to_api_level_map)
773 if min_sdk_version is not None:
774 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
775
T.R. Fullhart37e10522013-03-18 10:31:26 -0700776 cmd.extend([key + OPTIONS.public_key_suffix,
777 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800778 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700779
780 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700781 if password is not None:
782 password += "\n"
783 p.communicate(password)
784 if p.returncode != 0:
785 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
786
Doug Zongkereef39442009-04-02 12:14:19 -0700787
Doug Zongker37974732010-09-16 17:44:38 -0700788def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700789 """Check the data string passed against the max size limit, if
790 any, for the given target. Raise exception if the data is too big.
791 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700792
Dan Albert8b72aef2015-03-23 19:13:21 -0700793 if target.endswith(".img"):
794 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700795 mount_point = "/" + target
796
Ying Wangf8824af2014-06-03 14:07:27 -0700797 fs_type = None
798 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700799 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700800 if mount_point == "/userdata":
801 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700802 p = info_dict["fstab"][mount_point]
803 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800804 device = p.device
805 if "/" in device:
806 device = device[device.rfind("/")+1:]
807 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700808 if not fs_type or not limit:
809 return
Doug Zongkereef39442009-04-02 12:14:19 -0700810
Andrew Boie0f9aec82012-02-14 09:32:52 -0800811 size = len(data)
812 pct = float(size) * 100.0 / limit
813 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
814 if pct >= 99.0:
815 raise ExternalError(msg)
816 elif pct >= 95.0:
817 print
818 print " WARNING: ", msg
819 print
820 elif OPTIONS.verbose:
821 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700822
823
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800824def ReadApkCerts(tf_zip):
825 """Given a target_files ZipFile, parse the META/apkcerts.txt file
826 and return a {package: cert} dict."""
827 certmap = {}
828 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
829 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700830 if not line:
831 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800832 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
833 r'private_key="(.*)"$', line)
834 if m:
835 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700836 public_key_suffix_len = len(OPTIONS.public_key_suffix)
837 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800838 if cert in SPECIAL_CERT_STRINGS and not privkey:
839 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700840 elif (cert.endswith(OPTIONS.public_key_suffix) and
841 privkey.endswith(OPTIONS.private_key_suffix) and
842 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
843 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800844 else:
845 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
846 return certmap
847
848
Doug Zongkereef39442009-04-02 12:14:19 -0700849COMMON_DOCSTRING = """
850 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700851 Prepend <dir>/bin to the list of places to search for binaries
852 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700853
Doug Zongker05d3dea2009-06-22 11:32:31 -0700854 -s (--device_specific) <file>
855 Path to the python module containing device-specific
856 releasetools code.
857
Doug Zongker8bec09e2009-11-30 15:37:14 -0800858 -x (--extra) <key=value>
859 Add a key/value pair to the 'extras' dict, which device-specific
860 extension code may look at.
861
Doug Zongkereef39442009-04-02 12:14:19 -0700862 -v (--verbose)
863 Show command lines being executed.
864
865 -h (--help)
866 Display this usage message and exit.
867"""
868
869def Usage(docstring):
870 print docstring.rstrip("\n")
871 print COMMON_DOCSTRING
872
873
874def ParseOptions(argv,
875 docstring,
876 extra_opts="", extra_long_opts=(),
877 extra_option_handler=None):
878 """Parse the options in argv and return any arguments that aren't
879 flags. docstring is the calling module's docstring, to be displayed
880 for errors and -h. extra_opts and extra_long_opts are for flags
881 defined by the caller, which are processed by passing them to
882 extra_option_handler."""
883
884 try:
885 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800886 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800887 ["help", "verbose", "path=", "signapk_path=",
888 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700889 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700890 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
891 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800892 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700893 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700894 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700895 Usage(docstring)
896 print "**", str(err), "**"
897 sys.exit(2)
898
Doug Zongkereef39442009-04-02 12:14:19 -0700899 for o, a in opts:
900 if o in ("-h", "--help"):
901 Usage(docstring)
902 sys.exit()
903 elif o in ("-v", "--verbose"):
904 OPTIONS.verbose = True
905 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700906 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700907 elif o in ("--signapk_path",):
908 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800909 elif o in ("--signapk_shared_library_path",):
910 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700911 elif o in ("--extra_signapk_args",):
912 OPTIONS.extra_signapk_args = shlex.split(a)
913 elif o in ("--java_path",):
914 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700915 elif o in ("--java_args",):
916 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700917 elif o in ("--public_key_suffix",):
918 OPTIONS.public_key_suffix = a
919 elif o in ("--private_key_suffix",):
920 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800921 elif o in ("--boot_signer_path",):
922 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700923 elif o in ("--boot_signer_args",):
924 OPTIONS.boot_signer_args = shlex.split(a)
925 elif o in ("--verity_signer_path",):
926 OPTIONS.verity_signer_path = a
927 elif o in ("--verity_signer_args",):
928 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700929 elif o in ("-s", "--device_specific"):
930 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800931 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800932 key, value = a.split("=", 1)
933 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700934 else:
935 if extra_option_handler is None or not extra_option_handler(o, a):
936 assert False, "unknown option \"%s\"" % (o,)
937
Doug Zongker85448772014-09-09 14:59:20 -0700938 if OPTIONS.search_path:
939 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
940 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700941
942 return args
943
944
Doug Zongkerfc44a512014-08-26 13:10:25 -0700945def MakeTempFile(prefix=None, suffix=None):
946 """Make a temp file and add it to the list of things to be deleted
947 when Cleanup() is called. Return the filename."""
948 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
949 os.close(fd)
950 OPTIONS.tempfiles.append(fn)
951 return fn
952
953
Doug Zongkereef39442009-04-02 12:14:19 -0700954def Cleanup():
955 for i in OPTIONS.tempfiles:
956 if os.path.isdir(i):
957 shutil.rmtree(i)
958 else:
959 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700960
961
962class PasswordManager(object):
963 def __init__(self):
964 self.editor = os.getenv("EDITOR", None)
965 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
966
967 def GetPasswords(self, items):
968 """Get passwords corresponding to each string in 'items',
969 returning a dict. (The dict may have keys in addition to the
970 values in 'items'.)
971
972 Uses the passwords in $ANDROID_PW_FILE if available, letting the
973 user edit that file to add more needed passwords. If no editor is
974 available, or $ANDROID_PW_FILE isn't define, prompts the user
975 interactively in the ordinary way.
976 """
977
978 current = self.ReadFile()
979
980 first = True
981 while True:
982 missing = []
983 for i in items:
984 if i not in current or not current[i]:
985 missing.append(i)
986 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700987 if not missing:
988 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700989
990 for i in missing:
991 current[i] = ""
992
993 if not first:
994 print "key file %s still missing some passwords." % (self.pwfile,)
995 answer = raw_input("try to edit again? [y]> ").strip()
996 if answer and answer[0] not in 'yY':
997 raise RuntimeError("key passwords unavailable")
998 first = False
999
1000 current = self.UpdateAndReadFile(current)
1001
Dan Albert8b72aef2015-03-23 19:13:21 -07001002 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001003 """Prompt the user to enter a value (password) for each key in
1004 'current' whose value is fales. Returns a new dict with all the
1005 values.
1006 """
1007 result = {}
1008 for k, v in sorted(current.iteritems()):
1009 if v:
1010 result[k] = v
1011 else:
1012 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001013 result[k] = getpass.getpass(
1014 "Enter password for %s key> " % k).strip()
1015 if result[k]:
1016 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001017 return result
1018
1019 def UpdateAndReadFile(self, current):
1020 if not self.editor or not self.pwfile:
1021 return self.PromptResult(current)
1022
1023 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001024 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001025 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1026 f.write("# (Additional spaces are harmless.)\n\n")
1027
1028 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001029 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1030 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001031 f.write("[[[ %s ]]] %s\n" % (v, k))
1032 if not v and first_line is None:
1033 # position cursor on first line with no password.
1034 first_line = i + 4
1035 f.close()
1036
1037 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1038 _, _ = p.communicate()
1039
1040 return self.ReadFile()
1041
1042 def ReadFile(self):
1043 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001044 if self.pwfile is None:
1045 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001046 try:
1047 f = open(self.pwfile, "r")
1048 for line in f:
1049 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001050 if not line or line[0] == '#':
1051 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001052 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1053 if not m:
1054 print "failed to parse password file: ", line
1055 else:
1056 result[m.group(2)] = m.group(1)
1057 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001058 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001059 if e.errno != errno.ENOENT:
1060 print "error reading password file: ", str(e)
1061 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001062
1063
Dan Albert8e0178d2015-01-27 15:53:15 -08001064def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1065 compress_type=None):
1066 import datetime
1067
1068 # http://b/18015246
1069 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1070 # for files larger than 2GiB. We can work around this by adjusting their
1071 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1072 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1073 # it isn't clear to me exactly what circumstances cause this).
1074 # `zipfile.write()` must be used directly to work around this.
1075 #
1076 # This mess can be avoided if we port to python3.
1077 saved_zip64_limit = zipfile.ZIP64_LIMIT
1078 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1079
1080 if compress_type is None:
1081 compress_type = zip_file.compression
1082 if arcname is None:
1083 arcname = filename
1084
1085 saved_stat = os.stat(filename)
1086
1087 try:
1088 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1089 # file to be zipped and reset it when we're done.
1090 os.chmod(filename, perms)
1091
1092 # Use a fixed timestamp so the output is repeatable.
1093 epoch = datetime.datetime.fromtimestamp(0)
1094 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1095 os.utime(filename, (timestamp, timestamp))
1096
1097 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1098 finally:
1099 os.chmod(filename, saved_stat.st_mode)
1100 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1101 zipfile.ZIP64_LIMIT = saved_zip64_limit
1102
1103
Tao Bao58c1b962015-05-20 09:32:18 -07001104def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001105 compress_type=None):
1106 """Wrap zipfile.writestr() function to work around the zip64 limit.
1107
1108 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1109 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1110 when calling crc32(bytes).
1111
1112 But it still works fine to write a shorter string into a large zip file.
1113 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1114 when we know the string won't be too long.
1115 """
1116
1117 saved_zip64_limit = zipfile.ZIP64_LIMIT
1118 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1119
1120 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1121 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001122 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001123 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001124 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001125 else:
Tao Baof3282b42015-04-01 11:21:55 -07001126 zinfo = zinfo_or_arcname
1127
1128 # If compress_type is given, it overrides the value in zinfo.
1129 if compress_type is not None:
1130 zinfo.compress_type = compress_type
1131
Tao Bao58c1b962015-05-20 09:32:18 -07001132 # If perms is given, it has a priority.
1133 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001134 # If perms doesn't set the file type, mark it as a regular file.
1135 if perms & 0o770000 == 0:
1136 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001137 zinfo.external_attr = perms << 16
1138
Tao Baof3282b42015-04-01 11:21:55 -07001139 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001140 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1141
Dan Albert8b72aef2015-03-23 19:13:21 -07001142 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001143 zipfile.ZIP64_LIMIT = saved_zip64_limit
1144
1145
1146def ZipClose(zip_file):
1147 # http://b/18015246
1148 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1149 # central directory.
1150 saved_zip64_limit = zipfile.ZIP64_LIMIT
1151 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1152
1153 zip_file.close()
1154
1155 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001156
1157
1158class DeviceSpecificParams(object):
1159 module = None
1160 def __init__(self, **kwargs):
1161 """Keyword arguments to the constructor become attributes of this
1162 object, which is passed to all functions in the device-specific
1163 module."""
1164 for k, v in kwargs.iteritems():
1165 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001166 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001167
1168 if self.module is None:
1169 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001170 if not path:
1171 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001172 try:
1173 if os.path.isdir(path):
1174 info = imp.find_module("releasetools", [path])
1175 else:
1176 d, f = os.path.split(path)
1177 b, x = os.path.splitext(f)
1178 if x == ".py":
1179 f = b
1180 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001181 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001182 self.module = imp.load_module("device_specific", *info)
1183 except ImportError:
1184 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001185
1186 def _DoCall(self, function_name, *args, **kwargs):
1187 """Call the named function in the device-specific module, passing
1188 the given args and kwargs. The first argument to the call will be
1189 the DeviceSpecific object itself. If there is no module, or the
1190 module does not define the function, return the value of the
1191 'default' kwarg (which itself defaults to None)."""
1192 if self.module is None or not hasattr(self.module, function_name):
1193 return kwargs.get("default", None)
1194 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1195
1196 def FullOTA_Assertions(self):
1197 """Called after emitting the block of assertions at the top of a
1198 full OTA package. Implementations can add whatever additional
1199 assertions they like."""
1200 return self._DoCall("FullOTA_Assertions")
1201
Doug Zongkere5ff5902012-01-17 10:55:37 -08001202 def FullOTA_InstallBegin(self):
1203 """Called at the start of full OTA installation."""
1204 return self._DoCall("FullOTA_InstallBegin")
1205
Doug Zongker05d3dea2009-06-22 11:32:31 -07001206 def FullOTA_InstallEnd(self):
1207 """Called at the end of full OTA installation; typically this is
1208 used to install the image for the device's baseband processor."""
1209 return self._DoCall("FullOTA_InstallEnd")
1210
1211 def IncrementalOTA_Assertions(self):
1212 """Called after emitting the block of assertions at the top of an
1213 incremental OTA package. Implementations can add whatever
1214 additional assertions they like."""
1215 return self._DoCall("IncrementalOTA_Assertions")
1216
Doug Zongkere5ff5902012-01-17 10:55:37 -08001217 def IncrementalOTA_VerifyBegin(self):
1218 """Called at the start of the verification phase of incremental
1219 OTA installation; additional checks can be placed here to abort
1220 the script before any changes are made."""
1221 return self._DoCall("IncrementalOTA_VerifyBegin")
1222
Doug Zongker05d3dea2009-06-22 11:32:31 -07001223 def IncrementalOTA_VerifyEnd(self):
1224 """Called at the end of the verification phase of incremental OTA
1225 installation; additional checks can be placed here to abort the
1226 script before any changes are made."""
1227 return self._DoCall("IncrementalOTA_VerifyEnd")
1228
Doug Zongkere5ff5902012-01-17 10:55:37 -08001229 def IncrementalOTA_InstallBegin(self):
1230 """Called at the start of incremental OTA installation (after
1231 verification is complete)."""
1232 return self._DoCall("IncrementalOTA_InstallBegin")
1233
Doug Zongker05d3dea2009-06-22 11:32:31 -07001234 def IncrementalOTA_InstallEnd(self):
1235 """Called at the end of incremental OTA installation; typically
1236 this is used to install the image for the device's baseband
1237 processor."""
1238 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001239
Tao Bao9bc6bb22015-11-09 16:58:28 -08001240 def VerifyOTA_Assertions(self):
1241 return self._DoCall("VerifyOTA_Assertions")
1242
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001243class File(object):
1244 def __init__(self, name, data):
1245 self.name = name
1246 self.data = data
1247 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001248 self.sha1 = sha1(data).hexdigest()
1249
1250 @classmethod
1251 def FromLocalFile(cls, name, diskname):
1252 f = open(diskname, "rb")
1253 data = f.read()
1254 f.close()
1255 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001256
1257 def WriteToTemp(self):
1258 t = tempfile.NamedTemporaryFile()
1259 t.write(self.data)
1260 t.flush()
1261 return t
1262
Geremy Condra36bd3652014-02-06 19:45:10 -08001263 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001264 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001265
1266DIFF_PROGRAM_BY_EXT = {
1267 ".gz" : "imgdiff",
1268 ".zip" : ["imgdiff", "-z"],
1269 ".jar" : ["imgdiff", "-z"],
1270 ".apk" : ["imgdiff", "-z"],
1271 ".img" : "imgdiff",
1272 }
1273
1274class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001275 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001276 self.tf = tf
1277 self.sf = sf
1278 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001279 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001280
1281 def ComputePatch(self):
1282 """Compute the patch (as a string of data) needed to turn sf into
1283 tf. Returns the same tuple as GetPatch()."""
1284
1285 tf = self.tf
1286 sf = self.sf
1287
Doug Zongker24cd2802012-08-14 16:36:15 -07001288 if self.diff_program:
1289 diff_program = self.diff_program
1290 else:
1291 ext = os.path.splitext(tf.name)[1]
1292 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001293
1294 ttemp = tf.WriteToTemp()
1295 stemp = sf.WriteToTemp()
1296
1297 ext = os.path.splitext(tf.name)[1]
1298
1299 try:
1300 ptemp = tempfile.NamedTemporaryFile()
1301 if isinstance(diff_program, list):
1302 cmd = copy.copy(diff_program)
1303 else:
1304 cmd = [diff_program]
1305 cmd.append(stemp.name)
1306 cmd.append(ttemp.name)
1307 cmd.append(ptemp.name)
1308 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001309 err = []
1310 def run():
1311 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001312 if e:
1313 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001314 th = threading.Thread(target=run)
1315 th.start()
1316 th.join(timeout=300) # 5 mins
1317 if th.is_alive():
1318 print "WARNING: diff command timed out"
1319 p.terminate()
1320 th.join(5)
1321 if th.is_alive():
1322 p.kill()
1323 th.join()
1324
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001325 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001326 print "WARNING: failure running %s:\n%s\n" % (
1327 diff_program, "".join(err))
1328 self.patch = None
1329 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001330 diff = ptemp.read()
1331 finally:
1332 ptemp.close()
1333 stemp.close()
1334 ttemp.close()
1335
1336 self.patch = diff
1337 return self.tf, self.sf, self.patch
1338
1339
1340 def GetPatch(self):
1341 """Return a tuple (target_file, source_file, patch_data).
1342 patch_data may be None if ComputePatch hasn't been called, or if
1343 computing the patch failed."""
1344 return self.tf, self.sf, self.patch
1345
1346
1347def ComputeDifferences(diffs):
1348 """Call ComputePatch on all the Difference objects in 'diffs'."""
1349 print len(diffs), "diffs to compute"
1350
1351 # Do the largest files first, to try and reduce the long-pole effect.
1352 by_size = [(i.tf.size, i) for i in diffs]
1353 by_size.sort(reverse=True)
1354 by_size = [i[1] for i in by_size]
1355
1356 lock = threading.Lock()
1357 diff_iter = iter(by_size) # accessed under lock
1358
1359 def worker():
1360 try:
1361 lock.acquire()
1362 for d in diff_iter:
1363 lock.release()
1364 start = time.time()
1365 d.ComputePatch()
1366 dur = time.time() - start
1367 lock.acquire()
1368
1369 tf, sf, patch = d.GetPatch()
1370 if sf.name == tf.name:
1371 name = tf.name
1372 else:
1373 name = "%s (%s)" % (tf.name, sf.name)
1374 if patch is None:
1375 print "patching failed! %s" % (name,)
1376 else:
1377 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1378 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1379 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001380 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001381 print e
1382 raise
1383
1384 # start worker threads; wait for them all to finish.
1385 threads = [threading.Thread(target=worker)
1386 for i in range(OPTIONS.worker_threads)]
1387 for th in threads:
1388 th.start()
1389 while threads:
1390 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001391
1392
Dan Albert8b72aef2015-03-23 19:13:21 -07001393class BlockDifference(object):
1394 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Baob6568cd2016-06-11 12:19:23 -07001395 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001396 self.tgt = tgt
1397 self.src = src
1398 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001399 self.check_first_block = check_first_block
Tao Baob6568cd2016-06-11 12:19:23 -07001400 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001401
Tao Baodd2a5892015-03-12 12:32:37 -07001402 if version is None:
1403 version = 1
1404 if OPTIONS.info_dict:
1405 version = max(
1406 int(i) for i in
1407 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1408 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001409
1410 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Baob6568cd2016-06-11 12:19:23 -07001411 version=self.version,
1412 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001413 tmpdir = tempfile.mkdtemp()
1414 OPTIONS.tempfiles.append(tmpdir)
1415 self.path = os.path.join(tmpdir, partition)
1416 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001417 self._required_cache = b.max_stashed_size
Tao Baod4caaae2016-04-12 15:53:16 -07001418 self.touched_src_ranges = b.touched_src_ranges
1419 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001420
Tao Baoaac4ad52015-10-16 15:26:34 -07001421 if src is None:
1422 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1423 else:
1424 _, self.device = GetTypeAndDevice("/" + partition,
1425 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001426
Tao Baod8d14be2016-02-04 14:26:02 -08001427 @property
1428 def required_cache(self):
1429 return self._required_cache
1430
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001431 def WriteScript(self, script, output_zip, progress=None):
1432 if not self.src:
1433 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001434 script.Print("Patching %s image unconditionally..." % (self.partition,))
1435 else:
1436 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001437
Dan Albert8b72aef2015-03-23 19:13:21 -07001438 if progress:
1439 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001440 self._WriteUpdate(script, output_zip)
Tianjie Xu618a81e2016-03-25 15:01:33 -07001441 if OPTIONS.verify:
1442 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001443
Tao Bao9bc6bb22015-11-09 16:58:28 -08001444 def WriteStrictVerifyScript(self, script):
1445 """Verify all the blocks in the care_map, including clobbered blocks.
1446
1447 This differs from the WriteVerifyScript() function: a) it prints different
1448 error messages; b) it doesn't allow half-way updated images to pass the
1449 verification."""
1450
1451 partition = self.partition
1452 script.Print("Verifying %s..." % (partition,))
1453 ranges = self.tgt.care_map
1454 ranges_str = ranges.to_string_raw()
1455 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1456 'ui_print(" Verified.") || '
1457 'ui_print("\\"%s\\" has unexpected contents.");' % (
1458 self.device, ranges_str,
1459 self.tgt.TotalSha1(include_clobbered_blocks=True),
1460 self.device))
1461 script.AppendExtra("")
1462
Tao Baod4caaae2016-04-12 15:53:16 -07001463 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001464 partition = self.partition
Tao Bao962dfdc2016-04-14 15:58:05 -07001465
1466 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001467 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001468 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Bao962dfdc2016-04-14 15:58:05 -07001469
1470 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001471 else:
Tao Baod4caaae2016-04-12 15:53:16 -07001472 if touched_blocks_only and self.version >= 3:
1473 ranges = self.touched_src_ranges
1474 expected_sha1 = self.touched_src_sha1
1475 else:
1476 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1477 expected_sha1 = self.src.TotalSha1()
Tao Bao962dfdc2016-04-14 15:58:05 -07001478
1479 # No blocks to be checked, skipping.
1480 if not ranges:
1481 return
1482
Tao Bao5ece99d2015-05-12 11:42:31 -07001483 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001484 if self.version >= 4:
1485 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1486 'block_image_verify("%s", '
1487 'package_extract_file("%s.transfer.list"), '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001488 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baod4caaae2016-04-12 15:53:16 -07001489 self.device, ranges_str, expected_sha1,
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001490 self.device, partition, partition, partition))
1491 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001492 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1493 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001494 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001495 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baod4caaae2016-04-12 15:53:16 -07001496 self.device, ranges_str, expected_sha1,
Sami Tolvanene09d0962015-04-24 11:54:01 +01001497 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001498 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001499 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001500 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001501 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001502 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001503
Tianjie Xufc3422a2015-12-15 11:53:59 -08001504 if self.version >= 4:
1505
1506 # Bug: 21124327
1507 # When generating incrementals for the system and vendor partitions in
1508 # version 4 or newer, explicitly check the first block (which contains
1509 # the superblock) of the partition to see if it's what we expect. If
1510 # this check fails, give an explicit log message about the partition
1511 # having been remounted R/W (the most likely explanation).
1512 if self.check_first_block:
1513 script.AppendExtra('check_first_block("%s");' % (self.device,))
1514
1515 # If version >= 4, try block recovery before abort update
1516 script.AppendExtra((
1517 'ifelse (block_image_recover("{device}", "{ranges}") && '
1518 'block_image_verify("{device}", '
1519 'package_extract_file("{partition}.transfer.list"), '
1520 '"{partition}.new.dat", "{partition}.patch.dat"), '
1521 'ui_print("{partition} recovered successfully."), '
1522 'abort("{partition} partition fails to recover"));\n'
1523 'endif;').format(device=self.device, ranges=ranges_str,
1524 partition=partition))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001525
Tao Baodd2a5892015-03-12 12:32:37 -07001526 # Abort the OTA update. Note that the incremental OTA cannot be applied
1527 # even if it may match the checksum of the target partition.
1528 # a) If version < 3, operations like move and erase will make changes
1529 # unconditionally and damage the partition.
1530 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001531 else:
1532 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1533 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001534
Tao Bao5fcaaef2015-06-01 13:40:49 -07001535 def _WritePostInstallVerifyScript(self, script):
1536 partition = self.partition
1537 script.Print('Verifying the updated %s image...' % (partition,))
1538 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1539 ranges = self.tgt.care_map
1540 ranges_str = ranges.to_string_raw()
1541 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1542 self.device, ranges_str,
1543 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001544
1545 # Bug: 20881595
1546 # Verify that extended blocks are really zeroed out.
1547 if self.tgt.extended:
1548 ranges_str = self.tgt.extended.to_string_raw()
1549 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1550 self.device, ranges_str,
1551 self._HashZeroBlocks(self.tgt.extended.size())))
1552 script.Print('Verified the updated %s image.' % (partition,))
1553 script.AppendExtra(
1554 'else\n'
1555 ' abort("%s partition has unexpected non-zero contents after OTA '
1556 'update");\n'
1557 'endif;' % (partition,))
1558 else:
1559 script.Print('Verified the updated %s image.' % (partition,))
1560
Tao Bao5fcaaef2015-06-01 13:40:49 -07001561 script.AppendExtra(
1562 'else\n'
1563 ' abort("%s partition has unexpected contents after OTA update");\n'
1564 'endif;' % (partition,))
1565
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001566 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001567 ZipWrite(output_zip,
1568 '{}.transfer.list'.format(self.path),
1569 '{}.transfer.list'.format(self.partition))
1570 ZipWrite(output_zip,
1571 '{}.new.dat'.format(self.path),
1572 '{}.new.dat'.format(self.partition))
1573 ZipWrite(output_zip,
1574 '{}.patch.dat'.format(self.path),
1575 '{}.patch.dat'.format(self.partition),
1576 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001577
Dan Albert8e0178d2015-01-27 15:53:15 -08001578 call = ('block_image_update("{device}", '
1579 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xu618a81e2016-03-25 15:01:33 -07001580 '"{partition}.new.dat", "{partition}.patch.dat") ||\n'
1581 ' abort("Failed to update {partition} image.");'.format(
Dan Albert8e0178d2015-01-27 15:53:15 -08001582 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001583 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001584
Dan Albert8b72aef2015-03-23 19:13:21 -07001585 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001586 data = source.ReadRangeSet(ranges)
1587 ctx = sha1()
1588
1589 for p in data:
1590 ctx.update(p)
1591
1592 return ctx.hexdigest()
1593
Tao Baoe9b61912015-07-09 17:37:49 -07001594 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1595 """Return the hash value for all zero blocks."""
1596 zero_block = '\x00' * 4096
1597 ctx = sha1()
1598 for _ in range(num_blocks):
1599 ctx.update(zero_block)
1600
1601 return ctx.hexdigest()
1602
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001603
1604DataImage = blockimgdiff.DataImage
1605
Doug Zongker96a57e72010-09-26 14:57:41 -07001606# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001607PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001608 "ext4": "EMMC",
1609 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001610 "f2fs": "EMMC",
1611 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001612}
Doug Zongker96a57e72010-09-26 14:57:41 -07001613
1614def GetTypeAndDevice(mount_point, info):
1615 fstab = info["fstab"]
1616 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001617 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1618 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001619 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001620 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001621
1622
1623def ParseCertificate(data):
1624 """Parse a PEM-format certificate."""
1625 cert = []
1626 save = False
1627 for line in data.split("\n"):
1628 if "--END CERTIFICATE--" in line:
1629 break
1630 if save:
1631 cert.append(line)
1632 if "--BEGIN CERTIFICATE--" in line:
1633 save = True
1634 cert = "".join(cert).decode('base64')
1635 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001636
Doug Zongker412c02f2014-02-13 10:58:24 -08001637def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1638 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001639 """Generate a binary patch that creates the recovery image starting
1640 with the boot image. (Most of the space in these images is just the
1641 kernel, which is identical for the two, so the resulting patch
1642 should be efficient.) Add it to the output zip, along with a shell
1643 script that is run from init.rc on first boot to actually do the
1644 patching and install the new recovery image.
1645
1646 recovery_img and boot_img should be File objects for the
1647 corresponding images. info should be the dictionary returned by
1648 common.LoadInfoDict() on the input target_files.
1649 """
1650
Doug Zongker412c02f2014-02-13 10:58:24 -08001651 if info_dict is None:
1652 info_dict = OPTIONS.info_dict
1653
Tao Baof2cffbd2015-07-22 12:33:18 -07001654 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001655 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001656
Tao Baof2cffbd2015-07-22 12:33:18 -07001657 if full_recovery_image:
1658 output_sink("etc/recovery.img", recovery_img.data)
1659
1660 else:
1661 diff_program = ["imgdiff"]
1662 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1663 if os.path.exists(path):
1664 diff_program.append("-b")
1665 diff_program.append(path)
1666 bonus_args = "-b /system/etc/recovery-resource.dat"
1667 else:
1668 bonus_args = ""
1669
1670 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1671 _, _, patch = d.ComputePatch()
1672 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001673
Dan Albertebb19aa2015-03-27 19:11:53 -07001674 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001675 # The following GetTypeAndDevice()s need to use the path in the target
1676 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001677 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1678 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1679 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001680 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001681
Tao Baof2cffbd2015-07-22 12:33:18 -07001682 if full_recovery_image:
1683 sh = """#!/system/bin/sh
1684if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1685 applypatch /system/etc/recovery.img %(type)s:%(device)s %(sha1)s %(size)d && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1686else
1687 log -t recovery "Recovery image already installed"
1688fi
1689""" % {'type': recovery_type,
1690 'device': recovery_device,
1691 'sha1': recovery_img.sha1,
1692 'size': recovery_img.size}
1693 else:
1694 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001695if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1696 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"
1697else
1698 log -t recovery "Recovery image already installed"
1699fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001700""" % {'boot_size': boot_img.size,
1701 'boot_sha1': boot_img.sha1,
1702 'recovery_size': recovery_img.size,
1703 'recovery_sha1': recovery_img.sha1,
1704 'boot_type': boot_type,
1705 'boot_device': boot_device,
1706 'recovery_type': recovery_type,
1707 'recovery_device': recovery_device,
1708 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001709
1710 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001711 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001712 # target-files expects it to be, and put it there.
1713 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001714 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001715 if system_root_image:
1716 init_rc_dir = os.path.join(input_dir, "ROOT")
1717 else:
1718 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001719 init_rc_files = os.listdir(init_rc_dir)
1720 for init_rc_file in init_rc_files:
1721 if (not init_rc_file.startswith('init.') or
1722 not init_rc_file.endswith('.rc')):
1723 continue
1724
1725 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001726 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001727 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001728 if m:
1729 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001730 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001731 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001732
1733 if found:
1734 break
1735
1736 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001737
1738 output_sink(sh_location, sh)