blob: 62ccc452ae1af58d97a99edb9187028d0a8b012f [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
131 if "mkyaffs2_extra_flags" not in d:
132 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700133 d["mkyaffs2_extra_flags"] = read_helper(
134 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700135 except KeyError:
136 # ok if flags don't exist
137 pass
138
139 if "recovery_api_version" not in d:
140 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700141 d["recovery_api_version"] = read_helper(
142 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700143 except KeyError:
144 raise ValueError("can't find recovery API version in input target-files")
145
146 if "tool_extensions" not in d:
147 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800148 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700149 except KeyError:
150 # ok if extensions don't exist
151 pass
152
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800153 if "fstab_version" not in d:
154 d["fstab_version"] = "1"
155
Tao Bao84e75682015-07-19 02:38:53 -0700156 # A few properties are stored as links to the files in the out/ directory.
157 # It works fine with the build system. However, they are no longer available
158 # when (re)generating from target_files zip. If input_dir is not None, we
159 # are doing repacking. Redirect those properties to the actual files in the
160 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700161 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400162 # We carry a copy of file_contexts.bin under META/. If not available,
163 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700164 # to build images than the one running on device, such as when enabling
165 # system_root_image. In that case, we must have the one for image
166 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700167 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
168 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700169 if d.get("system_root_image") == "true":
170 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700171 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700172 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700173 if not os.path.exists(fc_config):
174 fc_config = None
175
176 if fc_config:
177 d["selinux_fc"] = fc_config
178
Tao Bao84e75682015-07-19 02:38:53 -0700179 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
180 if d.get("system_root_image") == "true":
181 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
182 d["ramdisk_fs_config"] = os.path.join(
183 input_dir, "META", "root_filesystem_config.txt")
184
Doug Zongker37974732010-09-16 17:44:38 -0700185 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800186 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700187 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700188 if not line:
189 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700190 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700191 if not value:
192 continue
Doug Zongker37974732010-09-16 17:44:38 -0700193 if name == "blocksize":
194 d[name] = value
195 else:
196 d[name + "_size"] = value
197 except KeyError:
198 pass
199
200 def makeint(key):
201 if key in d:
202 d[key] = int(d[key], 0)
203
204 makeint("recovery_api_version")
205 makeint("blocksize")
206 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700207 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700208 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700209 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700210 makeint("recovery_size")
211 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800212 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700213
Tao Bao48550cc2015-11-19 17:05:46 -0800214 if d.get("no_recovery", False) == "true":
215 d["fstab"] = None
216 else:
217 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
218 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800219 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700220 return d
221
Doug Zongkerc9253822014-02-04 12:17:58 -0800222def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700223 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800224 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700225 except KeyError:
226 print "Warning: could not find SYSTEM/build.prop in %s" % zip
227 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700228 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700229
Michael Runge6e836112014-04-15 17:40:21 -0700230def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700231 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700232 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700233 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700234 if not line or line.startswith("#"):
235 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700236 if "=" in line:
237 name, value = line.split("=", 1)
238 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700239 return d
240
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700241def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700242 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700243 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700244 self.mount_point = mount_point
245 self.fs_type = fs_type
246 self.device = device
247 self.length = length
248 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700249 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700250
251 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800252 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700253 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800254 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700255 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700256
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800257 if fstab_version == 1:
258 d = {}
259 for line in data.split("\n"):
260 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700261 if not line or line.startswith("#"):
262 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800263 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700264 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800265 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800266 options = None
267 if len(pieces) >= 4:
268 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700269 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800270 if len(pieces) >= 5:
271 options = pieces[4]
272 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700273 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800274 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800275 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700276 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700277
Dan Albert8b72aef2015-03-23 19:13:21 -0700278 mount_point = pieces[0]
279 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800280 if options:
281 options = options.split(",")
282 for i in options:
283 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700284 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800285 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700286 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800287
Dan Albert8b72aef2015-03-23 19:13:21 -0700288 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
289 device=pieces[2], length=length,
290 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800291
292 elif fstab_version == 2:
293 d = {}
294 for line in data.split("\n"):
295 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700296 if not line or line.startswith("#"):
297 continue
Tao Bao548eb762015-06-10 12:32:41 -0700298 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800299 pieces = line.split()
300 if len(pieces) != 5:
301 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
302
303 # Ignore entries that are managed by vold
304 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700305 if "voldmanaged=" in options:
306 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800307
308 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700309 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800310 options = options.split(",")
311 for i in options:
312 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700313 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800314 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800315 # Ignore all unknown options in the unified fstab
316 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800317
Tao Bao548eb762015-06-10 12:32:41 -0700318 mount_flags = pieces[3]
319 # Honor the SELinux context if present.
320 context = None
321 for i in mount_flags.split(","):
322 if i.startswith("context="):
323 context = i
324
Dan Albert8b72aef2015-03-23 19:13:21 -0700325 mount_point = pieces[1]
326 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700327 device=pieces[0], length=length,
328 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800329
330 else:
331 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
332
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700333 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700334 # system. Other areas assume system is always at "/system" so point /system
335 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700336 if system_root_image:
337 assert not d.has_key("/system") and d.has_key("/")
338 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700339 return d
340
341
Doug Zongker37974732010-09-16 17:44:38 -0700342def DumpInfoDict(d):
343 for k, v in sorted(d.items()):
344 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700345
Dan Albert8b72aef2015-03-23 19:13:21 -0700346
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700347def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
348 has_ramdisk=False):
349 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700350
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700351 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
352 'sourcedir'), and turn them into a boot image. Return the image data, or
353 None if sourcedir does not appear to contains files for building the
354 requested image."""
355
356 def make_ramdisk():
357 ramdisk_img = tempfile.NamedTemporaryFile()
358
359 if os.access(fs_config_file, os.F_OK):
360 cmd = ["mkbootfs", "-f", fs_config_file,
361 os.path.join(sourcedir, "RAMDISK")]
362 else:
363 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
364 p1 = Run(cmd, stdout=subprocess.PIPE)
365 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
366
367 p2.wait()
368 p1.wait()
369 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
370 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
371
372 return ramdisk_img
373
374 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
375 return None
376
377 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700378 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700379
Doug Zongkerd5131602012-08-02 14:46:42 -0700380 if info_dict is None:
381 info_dict = OPTIONS.info_dict
382
Doug Zongkereef39442009-04-02 12:14:19 -0700383 img = tempfile.NamedTemporaryFile()
384
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700385 if has_ramdisk:
386 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700387
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800388 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
389 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
390
391 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700392
Benoit Fradina45a8682014-07-14 21:00:43 +0200393 fn = os.path.join(sourcedir, "second")
394 if os.access(fn, os.F_OK):
395 cmd.append("--second")
396 cmd.append(fn)
397
Doug Zongker171f1cd2009-06-15 22:36:37 -0700398 fn = os.path.join(sourcedir, "cmdline")
399 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700400 cmd.append("--cmdline")
401 cmd.append(open(fn).read().rstrip("\n"))
402
403 fn = os.path.join(sourcedir, "base")
404 if os.access(fn, os.F_OK):
405 cmd.append("--base")
406 cmd.append(open(fn).read().rstrip("\n"))
407
Ying Wang4de6b5b2010-08-25 14:29:34 -0700408 fn = os.path.join(sourcedir, "pagesize")
409 if os.access(fn, os.F_OK):
410 cmd.append("--pagesize")
411 cmd.append(open(fn).read().rstrip("\n"))
412
Doug Zongkerd5131602012-08-02 14:46:42 -0700413 args = info_dict.get("mkbootimg_args", None)
414 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700415 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700416
Sami Tolvanen3303d902016-03-15 16:49:30 +0000417 args = info_dict.get("mkbootimg_version_args", None)
418 if args and args.strip():
419 cmd.extend(shlex.split(args))
420
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700421 if has_ramdisk:
422 cmd.extend(["--ramdisk", ramdisk_img.name])
423
Tao Baod95e9fd2015-03-29 23:07:41 -0700424 img_unsigned = None
425 if info_dict.get("vboot", None):
426 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700427 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700428 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700429 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700430
431 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700432 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700433 assert p.returncode == 0, "mkbootimg of %s image failed" % (
434 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700435
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100436 if (info_dict.get("boot_signer", None) == "true" and
437 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700438 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700439 cmd = [OPTIONS.boot_signer_path]
440 cmd.extend(OPTIONS.boot_signer_args)
441 cmd.extend([path, img.name,
442 info_dict["verity_key"] + ".pk8",
443 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700444 p = Run(cmd, stdout=subprocess.PIPE)
445 p.communicate()
446 assert p.returncode == 0, "boot_signer of %s image failed" % path
447
Tao Baod95e9fd2015-03-29 23:07:41 -0700448 # Sign the image if vboot is non-empty.
449 elif info_dict.get("vboot", None):
450 path = "/" + os.path.basename(sourcedir).lower()
451 img_keyblock = tempfile.NamedTemporaryFile()
452 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
453 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700454 info_dict["vboot_key"] + ".vbprivk",
455 info_dict["vboot_subkey"] + ".vbprivk",
456 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700457 img.name]
458 p = Run(cmd, stdout=subprocess.PIPE)
459 p.communicate()
460 assert p.returncode == 0, "vboot_signer of %s image failed" % path
461
Tao Baof3282b42015-04-01 11:21:55 -0700462 # Clean up the temp files.
463 img_unsigned.close()
464 img_keyblock.close()
465
Doug Zongkereef39442009-04-02 12:14:19 -0700466 img.seek(os.SEEK_SET, 0)
467 data = img.read()
468
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700469 if has_ramdisk:
470 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700471 img.close()
472
473 return data
474
475
David Zeuthend995f4b2016-01-29 16:59:17 -0500476def _BuildBvbBootableImage(sourcedir, fs_config_file, system_img_path,
477 info_dict=None, has_ramdisk=False):
478 """Build a bootable image compatible with Brillo Verified Boot from the
479 specified sourcedir.
480
481 Take a kernel, cmdline, system image path, and optionally a ramdisk
482 directory from the input (in 'sourcedir'), and turn them into a boot
483 image. Return the image data, or None if sourcedir does not appear
484 to contains files for building the requested image.
485 """
486
487 def make_ramdisk():
488 ramdisk_img = tempfile.NamedTemporaryFile()
489
490 if os.access(fs_config_file, os.F_OK):
491 cmd = ["mkbootfs", "-f", fs_config_file,
492 os.path.join(sourcedir, "RAMDISK")]
493 else:
494 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
495 p1 = Run(cmd, stdout=subprocess.PIPE)
496 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
497
498 p2.wait()
499 p1.wait()
500 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
501 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
502
503 return ramdisk_img
504
505 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
506 return None
507
508 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
509 return None
510
511 if info_dict is None:
512 info_dict = OPTIONS.info_dict
513
514 img = tempfile.NamedTemporaryFile()
515
516 if has_ramdisk:
517 ramdisk_img = make_ramdisk()
518
519 # use BVBTOOL from environ, or "bvbtool" if empty or not set
520 bvbtool = os.getenv('BVBTOOL') or "bvbtool"
521
522 # First, create boot.img.
523 cmd = [bvbtool, "make_boot_image"]
524
525 fn = os.path.join(sourcedir, "cmdline")
526 if os.access(fn, os.F_OK):
527 cmd.append("--kernel_cmdline")
528 cmd.append(open(fn).read().rstrip("\n"))
529
530 cmd.extend(["--kernel", os.path.join(sourcedir, "kernel")])
531
532 if has_ramdisk:
533 cmd.extend(["--initrd", ramdisk_img.name])
534
535 cmd.extend(["--rootfs_with_hashes", system_img_path])
536
537 args = info_dict.get("board_bvb_make_boot_image_args", None)
538 if args and args.strip():
539 cmd.extend(shlex.split(args))
540
541 rollback_index = info_dict.get("board_bvb_rollback_index", None)
542 if rollback_index and rollback_index.strip():
543 cmd.extend(["--rollback_index", rollback_index.strip()])
544
545 cmd.extend(["--output", img.name])
546
547 p = Run(cmd, stdout=subprocess.PIPE)
548 p.communicate()
549 assert p.returncode == 0, "bvbtool make_boot_image of %s image failed" % (
550 os.path.basename(sourcedir),)
551
552 # Then, sign boot.img.
553 cmd = [bvbtool, "sign_boot_image", "--image", img.name]
554
555 algorithm = info_dict.get("board_bvb_algorithm", None)
556 key_path = info_dict.get("board_bvb_key_path", None)
557 if algorithm and algorithm.strip() and key_path and key_path.strip():
558 cmd.extend(["--algorithm", algorithm, "--key", key_path])
559 else:
560 cmd.extend(["--algorithm", "SHA256_RSA4096"])
561 cmd.extend(["--key", "system/bvb/test/testkey_rsa4096.pem"])
562
563 args = info_dict.get("board_bvb_sign_boot_image_args", None)
564 if args and args.strip():
565 cmd.extend(shlex.split(args))
566
567 p = Run(cmd, stdout=subprocess.PIPE)
568 p.communicate()
569 assert p.returncode == 0, "bvbtool sign_boot_image of %s image failed" % (
570 os.path.basename(sourcedir),)
571
572 img.seek(os.SEEK_SET, 0)
573 data = img.read()
574
575 if has_ramdisk:
576 ramdisk_img.close()
577 img.close()
578
579 return data
580
581
Doug Zongkerd5131602012-08-02 14:46:42 -0700582def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
David Zeuthend995f4b2016-01-29 16:59:17 -0500583 info_dict=None, system_img_path=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700584 """Return a File object with the desired bootable image.
585
586 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
587 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
588 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700589
Doug Zongker55d93282011-01-25 17:03:34 -0800590 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
591 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700592 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800593 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700594
595 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
596 if os.path.exists(prebuilt_path):
597 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
598 return File.FromLocalFile(name, prebuilt_path)
599
600 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700601
602 if info_dict is None:
603 info_dict = OPTIONS.info_dict
604
605 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800606 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
607 # for recovery.
608 has_ramdisk = (info_dict.get("system_root_image") != "true" or
609 prebuilt_name != "boot.img" or
610 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700611
Doug Zongker6f1d0312014-08-22 08:07:12 -0700612 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthend995f4b2016-01-29 16:59:17 -0500613 if info_dict.get("board_bvb_enable", None) == "true":
614 data = _BuildBvbBootableImage(os.path.join(unpack_dir, tree_subdir),
615 os.path.join(unpack_dir, fs_config),
616 system_img_path, info_dict, has_ramdisk)
617 else:
618 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
619 os.path.join(unpack_dir, fs_config),
620 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700621 if data:
622 return File(name, data)
623 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800624
Doug Zongkereef39442009-04-02 12:14:19 -0700625
Doug Zongker75f17362009-12-08 13:46:44 -0800626def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800627 """Unzip the given archive into a temporary directory and return the name.
628
629 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
630 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
631
632 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
633 main file), open for reading.
634 """
Doug Zongkereef39442009-04-02 12:14:19 -0700635
636 tmp = tempfile.mkdtemp(prefix="targetfiles-")
637 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800638
639 def unzip_to_dir(filename, dirname):
640 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
641 if pattern is not None:
642 cmd.append(pattern)
643 p = Run(cmd, stdout=subprocess.PIPE)
644 p.communicate()
645 if p.returncode != 0:
646 raise ExternalError("failed to unzip input target-files \"%s\"" %
647 (filename,))
648
649 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
650 if m:
651 unzip_to_dir(m.group(1), tmp)
652 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
653 filename = m.group(1)
654 else:
655 unzip_to_dir(filename, tmp)
656
657 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700658
659
660def GetKeyPasswords(keylist):
661 """Given a list of keys, prompt the user to enter passwords for
662 those which require them. Return a {key: password} dict. password
663 will be None if the key has no password."""
664
Doug Zongker8ce7c252009-05-22 13:34:54 -0700665 no_passwords = []
666 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700667 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700668 devnull = open("/dev/null", "w+b")
669 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800670 # We don't need a password for things that aren't really keys.
671 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700672 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700673 continue
674
T.R. Fullhart37e10522013-03-18 10:31:26 -0700675 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700676 "-inform", "DER", "-nocrypt"],
677 stdin=devnull.fileno(),
678 stdout=devnull.fileno(),
679 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700680 p.communicate()
681 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700682 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700683 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700684 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700685 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
686 "-inform", "DER", "-passin", "pass:"],
687 stdin=devnull.fileno(),
688 stdout=devnull.fileno(),
689 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700690 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700691 if p.returncode == 0:
692 # Encrypted key with empty string as password.
693 key_passwords[k] = ''
694 elif stderr.startswith('Error decrypting key'):
695 # Definitely encrypted key.
696 # It would have said "Error reading key" if it didn't parse correctly.
697 need_passwords.append(k)
698 else:
699 # Potentially, a type of key that openssl doesn't understand.
700 # We'll let the routines in signapk.jar handle it.
701 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700702 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700703
T.R. Fullhart37e10522013-03-18 10:31:26 -0700704 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700705 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700706 return key_passwords
707
708
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800709def GetMinSdkVersion(apk_name):
710 """Get the minSdkVersion delared in the APK. This can be both a decimal number
711 (API Level) or a codename.
712 """
713
714 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
715 output, err = p.communicate()
716 if err:
717 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
718 % (p.returncode,))
719
720 for line in output.split("\n"):
721 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
722 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
723 if m:
724 return m.group(1)
725 raise ExternalError("No minSdkVersion returned by aapt")
726
727
728def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
729 """Get the minSdkVersion declared in the APK as a number (API Level). If
730 minSdkVersion is set to a codename, it is translated to a number using the
731 provided map.
732 """
733
734 version = GetMinSdkVersion(apk_name)
735 try:
736 return int(version)
737 except ValueError:
738 # Not a decimal number. Codename?
739 if version in codename_to_api_level_map:
740 return codename_to_api_level_map[version]
741 else:
742 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
743 % (version, codename_to_api_level_map))
744
745
746def SignFile(input_name, output_name, key, password, min_api_level=None,
747 codename_to_api_level_map=dict(),
748 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700749 """Sign the input_name zip/jar/apk, producing output_name. Use the
750 given key and password (the latter may be None if the key does not
751 have a password.
752
Doug Zongker951495f2009-08-14 12:44:19 -0700753 If whole_file is true, use the "-w" option to SignApk to embed a
754 signature that covers the whole file in the archive comment of the
755 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800756
757 min_api_level is the API Level (int) of the oldest platform this file may end
758 up on. If not specified for an APK, the API Level is obtained by interpreting
759 the minSdkVersion attribute of the APK's AndroidManifest.xml.
760
761 codename_to_api_level_map is needed to translate the codename which may be
762 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700763 """
Doug Zongker951495f2009-08-14 12:44:19 -0700764
Alex Klyubin9667b182015-12-10 13:38:50 -0800765 java_library_path = os.path.join(
766 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
767
768 cmd = [OPTIONS.java_path, OPTIONS.java_args,
769 "-Djava.library.path=" + java_library_path,
770 "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700771 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
772 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700773 if whole_file:
774 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800775
776 min_sdk_version = min_api_level
777 if min_sdk_version is None:
778 if not whole_file:
779 min_sdk_version = GetMinSdkVersionInt(
780 input_name, codename_to_api_level_map)
781 if min_sdk_version is not None:
782 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
783
T.R. Fullhart37e10522013-03-18 10:31:26 -0700784 cmd.extend([key + OPTIONS.public_key_suffix,
785 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800786 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700787
788 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700789 if password is not None:
790 password += "\n"
791 p.communicate(password)
792 if p.returncode != 0:
793 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
794
Doug Zongkereef39442009-04-02 12:14:19 -0700795
Doug Zongker37974732010-09-16 17:44:38 -0700796def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700797 """Check the data string passed against the max size limit, if
798 any, for the given target. Raise exception if the data is too big.
799 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700800
Dan Albert8b72aef2015-03-23 19:13:21 -0700801 if target.endswith(".img"):
802 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700803 mount_point = "/" + target
804
Ying Wangf8824af2014-06-03 14:07:27 -0700805 fs_type = None
806 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700807 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700808 if mount_point == "/userdata":
809 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700810 p = info_dict["fstab"][mount_point]
811 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800812 device = p.device
813 if "/" in device:
814 device = device[device.rfind("/")+1:]
815 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700816 if not fs_type or not limit:
817 return
Doug Zongkereef39442009-04-02 12:14:19 -0700818
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700819 if fs_type == "yaffs2":
820 # image size should be increased by 1/64th to account for the
821 # spare area (64 bytes per 2k page)
822 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800823 size = len(data)
824 pct = float(size) * 100.0 / limit
825 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
826 if pct >= 99.0:
827 raise ExternalError(msg)
828 elif pct >= 95.0:
829 print
830 print " WARNING: ", msg
831 print
832 elif OPTIONS.verbose:
833 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700834
835
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800836def ReadApkCerts(tf_zip):
837 """Given a target_files ZipFile, parse the META/apkcerts.txt file
838 and return a {package: cert} dict."""
839 certmap = {}
840 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
841 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700842 if not line:
843 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800844 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
845 r'private_key="(.*)"$', line)
846 if m:
847 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700848 public_key_suffix_len = len(OPTIONS.public_key_suffix)
849 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800850 if cert in SPECIAL_CERT_STRINGS and not privkey:
851 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700852 elif (cert.endswith(OPTIONS.public_key_suffix) and
853 privkey.endswith(OPTIONS.private_key_suffix) and
854 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
855 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800856 else:
857 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
858 return certmap
859
860
Doug Zongkereef39442009-04-02 12:14:19 -0700861COMMON_DOCSTRING = """
862 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700863 Prepend <dir>/bin to the list of places to search for binaries
864 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700865
Doug Zongker05d3dea2009-06-22 11:32:31 -0700866 -s (--device_specific) <file>
867 Path to the python module containing device-specific
868 releasetools code.
869
Doug Zongker8bec09e2009-11-30 15:37:14 -0800870 -x (--extra) <key=value>
871 Add a key/value pair to the 'extras' dict, which device-specific
872 extension code may look at.
873
Doug Zongkereef39442009-04-02 12:14:19 -0700874 -v (--verbose)
875 Show command lines being executed.
876
877 -h (--help)
878 Display this usage message and exit.
879"""
880
881def Usage(docstring):
882 print docstring.rstrip("\n")
883 print COMMON_DOCSTRING
884
885
886def ParseOptions(argv,
887 docstring,
888 extra_opts="", extra_long_opts=(),
889 extra_option_handler=None):
890 """Parse the options in argv and return any arguments that aren't
891 flags. docstring is the calling module's docstring, to be displayed
892 for errors and -h. extra_opts and extra_long_opts are for flags
893 defined by the caller, which are processed by passing them to
894 extra_option_handler."""
895
896 try:
897 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800898 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800899 ["help", "verbose", "path=", "signapk_path=",
900 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700901 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700902 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
903 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800904 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700905 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700906 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700907 Usage(docstring)
908 print "**", str(err), "**"
909 sys.exit(2)
910
Doug Zongkereef39442009-04-02 12:14:19 -0700911 for o, a in opts:
912 if o in ("-h", "--help"):
913 Usage(docstring)
914 sys.exit()
915 elif o in ("-v", "--verbose"):
916 OPTIONS.verbose = True
917 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700918 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700919 elif o in ("--signapk_path",):
920 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800921 elif o in ("--signapk_shared_library_path",):
922 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700923 elif o in ("--extra_signapk_args",):
924 OPTIONS.extra_signapk_args = shlex.split(a)
925 elif o in ("--java_path",):
926 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700927 elif o in ("--java_args",):
928 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700929 elif o in ("--public_key_suffix",):
930 OPTIONS.public_key_suffix = a
931 elif o in ("--private_key_suffix",):
932 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800933 elif o in ("--boot_signer_path",):
934 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700935 elif o in ("--boot_signer_args",):
936 OPTIONS.boot_signer_args = shlex.split(a)
937 elif o in ("--verity_signer_path",):
938 OPTIONS.verity_signer_path = a
939 elif o in ("--verity_signer_args",):
940 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700941 elif o in ("-s", "--device_specific"):
942 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800943 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800944 key, value = a.split("=", 1)
945 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700946 else:
947 if extra_option_handler is None or not extra_option_handler(o, a):
948 assert False, "unknown option \"%s\"" % (o,)
949
Doug Zongker85448772014-09-09 14:59:20 -0700950 if OPTIONS.search_path:
951 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
952 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700953
954 return args
955
956
Doug Zongkerfc44a512014-08-26 13:10:25 -0700957def MakeTempFile(prefix=None, suffix=None):
958 """Make a temp file and add it to the list of things to be deleted
959 when Cleanup() is called. Return the filename."""
960 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
961 os.close(fd)
962 OPTIONS.tempfiles.append(fn)
963 return fn
964
965
Doug Zongkereef39442009-04-02 12:14:19 -0700966def Cleanup():
967 for i in OPTIONS.tempfiles:
968 if os.path.isdir(i):
969 shutil.rmtree(i)
970 else:
971 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700972
973
974class PasswordManager(object):
975 def __init__(self):
976 self.editor = os.getenv("EDITOR", None)
977 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
978
979 def GetPasswords(self, items):
980 """Get passwords corresponding to each string in 'items',
981 returning a dict. (The dict may have keys in addition to the
982 values in 'items'.)
983
984 Uses the passwords in $ANDROID_PW_FILE if available, letting the
985 user edit that file to add more needed passwords. If no editor is
986 available, or $ANDROID_PW_FILE isn't define, prompts the user
987 interactively in the ordinary way.
988 """
989
990 current = self.ReadFile()
991
992 first = True
993 while True:
994 missing = []
995 for i in items:
996 if i not in current or not current[i]:
997 missing.append(i)
998 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700999 if not missing:
1000 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001001
1002 for i in missing:
1003 current[i] = ""
1004
1005 if not first:
1006 print "key file %s still missing some passwords." % (self.pwfile,)
1007 answer = raw_input("try to edit again? [y]> ").strip()
1008 if answer and answer[0] not in 'yY':
1009 raise RuntimeError("key passwords unavailable")
1010 first = False
1011
1012 current = self.UpdateAndReadFile(current)
1013
Dan Albert8b72aef2015-03-23 19:13:21 -07001014 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001015 """Prompt the user to enter a value (password) for each key in
1016 'current' whose value is fales. Returns a new dict with all the
1017 values.
1018 """
1019 result = {}
1020 for k, v in sorted(current.iteritems()):
1021 if v:
1022 result[k] = v
1023 else:
1024 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001025 result[k] = getpass.getpass(
1026 "Enter password for %s key> " % k).strip()
1027 if result[k]:
1028 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001029 return result
1030
1031 def UpdateAndReadFile(self, current):
1032 if not self.editor or not self.pwfile:
1033 return self.PromptResult(current)
1034
1035 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001036 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001037 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1038 f.write("# (Additional spaces are harmless.)\n\n")
1039
1040 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001041 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1042 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001043 f.write("[[[ %s ]]] %s\n" % (v, k))
1044 if not v and first_line is None:
1045 # position cursor on first line with no password.
1046 first_line = i + 4
1047 f.close()
1048
1049 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1050 _, _ = p.communicate()
1051
1052 return self.ReadFile()
1053
1054 def ReadFile(self):
1055 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001056 if self.pwfile is None:
1057 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001058 try:
1059 f = open(self.pwfile, "r")
1060 for line in f:
1061 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001062 if not line or line[0] == '#':
1063 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001064 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1065 if not m:
1066 print "failed to parse password file: ", line
1067 else:
1068 result[m.group(2)] = m.group(1)
1069 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001070 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001071 if e.errno != errno.ENOENT:
1072 print "error reading password file: ", str(e)
1073 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001074
1075
Dan Albert8e0178d2015-01-27 15:53:15 -08001076def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1077 compress_type=None):
1078 import datetime
1079
1080 # http://b/18015246
1081 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1082 # for files larger than 2GiB. We can work around this by adjusting their
1083 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1084 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1085 # it isn't clear to me exactly what circumstances cause this).
1086 # `zipfile.write()` must be used directly to work around this.
1087 #
1088 # This mess can be avoided if we port to python3.
1089 saved_zip64_limit = zipfile.ZIP64_LIMIT
1090 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1091
1092 if compress_type is None:
1093 compress_type = zip_file.compression
1094 if arcname is None:
1095 arcname = filename
1096
1097 saved_stat = os.stat(filename)
1098
1099 try:
1100 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1101 # file to be zipped and reset it when we're done.
1102 os.chmod(filename, perms)
1103
1104 # Use a fixed timestamp so the output is repeatable.
1105 epoch = datetime.datetime.fromtimestamp(0)
1106 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1107 os.utime(filename, (timestamp, timestamp))
1108
1109 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1110 finally:
1111 os.chmod(filename, saved_stat.st_mode)
1112 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1113 zipfile.ZIP64_LIMIT = saved_zip64_limit
1114
1115
Tao Bao58c1b962015-05-20 09:32:18 -07001116def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001117 compress_type=None):
1118 """Wrap zipfile.writestr() function to work around the zip64 limit.
1119
1120 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1121 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1122 when calling crc32(bytes).
1123
1124 But it still works fine to write a shorter string into a large zip file.
1125 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1126 when we know the string won't be too long.
1127 """
1128
1129 saved_zip64_limit = zipfile.ZIP64_LIMIT
1130 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1131
1132 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1133 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001134 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001135 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001136 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001137 else:
Tao Baof3282b42015-04-01 11:21:55 -07001138 zinfo = zinfo_or_arcname
1139
1140 # If compress_type is given, it overrides the value in zinfo.
1141 if compress_type is not None:
1142 zinfo.compress_type = compress_type
1143
Tao Bao58c1b962015-05-20 09:32:18 -07001144 # If perms is given, it has a priority.
1145 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001146 # If perms doesn't set the file type, mark it as a regular file.
1147 if perms & 0o770000 == 0:
1148 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001149 zinfo.external_attr = perms << 16
1150
Tao Baof3282b42015-04-01 11:21:55 -07001151 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001152 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1153
Dan Albert8b72aef2015-03-23 19:13:21 -07001154 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001155 zipfile.ZIP64_LIMIT = saved_zip64_limit
1156
1157
1158def ZipClose(zip_file):
1159 # http://b/18015246
1160 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1161 # central directory.
1162 saved_zip64_limit = zipfile.ZIP64_LIMIT
1163 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1164
1165 zip_file.close()
1166
1167 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001168
1169
1170class DeviceSpecificParams(object):
1171 module = None
1172 def __init__(self, **kwargs):
1173 """Keyword arguments to the constructor become attributes of this
1174 object, which is passed to all functions in the device-specific
1175 module."""
1176 for k, v in kwargs.iteritems():
1177 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001178 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001179
1180 if self.module is None:
1181 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001182 if not path:
1183 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001184 try:
1185 if os.path.isdir(path):
1186 info = imp.find_module("releasetools", [path])
1187 else:
1188 d, f = os.path.split(path)
1189 b, x = os.path.splitext(f)
1190 if x == ".py":
1191 f = b
1192 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001193 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001194 self.module = imp.load_module("device_specific", *info)
1195 except ImportError:
1196 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001197
1198 def _DoCall(self, function_name, *args, **kwargs):
1199 """Call the named function in the device-specific module, passing
1200 the given args and kwargs. The first argument to the call will be
1201 the DeviceSpecific object itself. If there is no module, or the
1202 module does not define the function, return the value of the
1203 'default' kwarg (which itself defaults to None)."""
1204 if self.module is None or not hasattr(self.module, function_name):
1205 return kwargs.get("default", None)
1206 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1207
1208 def FullOTA_Assertions(self):
1209 """Called after emitting the block of assertions at the top of a
1210 full OTA package. Implementations can add whatever additional
1211 assertions they like."""
1212 return self._DoCall("FullOTA_Assertions")
1213
Doug Zongkere5ff5902012-01-17 10:55:37 -08001214 def FullOTA_InstallBegin(self):
1215 """Called at the start of full OTA installation."""
1216 return self._DoCall("FullOTA_InstallBegin")
1217
Doug Zongker05d3dea2009-06-22 11:32:31 -07001218 def FullOTA_InstallEnd(self):
1219 """Called at the end of full OTA installation; typically this is
1220 used to install the image for the device's baseband processor."""
1221 return self._DoCall("FullOTA_InstallEnd")
1222
1223 def IncrementalOTA_Assertions(self):
1224 """Called after emitting the block of assertions at the top of an
1225 incremental OTA package. Implementations can add whatever
1226 additional assertions they like."""
1227 return self._DoCall("IncrementalOTA_Assertions")
1228
Doug Zongkere5ff5902012-01-17 10:55:37 -08001229 def IncrementalOTA_VerifyBegin(self):
1230 """Called at the start of the verification phase of incremental
1231 OTA installation; additional checks can be placed here to abort
1232 the script before any changes are made."""
1233 return self._DoCall("IncrementalOTA_VerifyBegin")
1234
Doug Zongker05d3dea2009-06-22 11:32:31 -07001235 def IncrementalOTA_VerifyEnd(self):
1236 """Called at the end of the verification phase of incremental OTA
1237 installation; additional checks can be placed here to abort the
1238 script before any changes are made."""
1239 return self._DoCall("IncrementalOTA_VerifyEnd")
1240
Doug Zongkere5ff5902012-01-17 10:55:37 -08001241 def IncrementalOTA_InstallBegin(self):
1242 """Called at the start of incremental OTA installation (after
1243 verification is complete)."""
1244 return self._DoCall("IncrementalOTA_InstallBegin")
1245
Doug Zongker05d3dea2009-06-22 11:32:31 -07001246 def IncrementalOTA_InstallEnd(self):
1247 """Called at the end of incremental OTA installation; typically
1248 this is used to install the image for the device's baseband
1249 processor."""
1250 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001251
Tao Bao9bc6bb22015-11-09 16:58:28 -08001252 def VerifyOTA_Assertions(self):
1253 return self._DoCall("VerifyOTA_Assertions")
1254
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001255class File(object):
1256 def __init__(self, name, data):
1257 self.name = name
1258 self.data = data
1259 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001260 self.sha1 = sha1(data).hexdigest()
1261
1262 @classmethod
1263 def FromLocalFile(cls, name, diskname):
1264 f = open(diskname, "rb")
1265 data = f.read()
1266 f.close()
1267 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001268
1269 def WriteToTemp(self):
1270 t = tempfile.NamedTemporaryFile()
1271 t.write(self.data)
1272 t.flush()
1273 return t
1274
Geremy Condra36bd3652014-02-06 19:45:10 -08001275 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001276 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001277
1278DIFF_PROGRAM_BY_EXT = {
1279 ".gz" : "imgdiff",
1280 ".zip" : ["imgdiff", "-z"],
1281 ".jar" : ["imgdiff", "-z"],
1282 ".apk" : ["imgdiff", "-z"],
1283 ".img" : "imgdiff",
1284 }
1285
1286class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001287 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001288 self.tf = tf
1289 self.sf = sf
1290 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001291 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001292
1293 def ComputePatch(self):
1294 """Compute the patch (as a string of data) needed to turn sf into
1295 tf. Returns the same tuple as GetPatch()."""
1296
1297 tf = self.tf
1298 sf = self.sf
1299
Doug Zongker24cd2802012-08-14 16:36:15 -07001300 if self.diff_program:
1301 diff_program = self.diff_program
1302 else:
1303 ext = os.path.splitext(tf.name)[1]
1304 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001305
1306 ttemp = tf.WriteToTemp()
1307 stemp = sf.WriteToTemp()
1308
1309 ext = os.path.splitext(tf.name)[1]
1310
1311 try:
1312 ptemp = tempfile.NamedTemporaryFile()
1313 if isinstance(diff_program, list):
1314 cmd = copy.copy(diff_program)
1315 else:
1316 cmd = [diff_program]
1317 cmd.append(stemp.name)
1318 cmd.append(ttemp.name)
1319 cmd.append(ptemp.name)
1320 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001321 err = []
1322 def run():
1323 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001324 if e:
1325 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001326 th = threading.Thread(target=run)
1327 th.start()
1328 th.join(timeout=300) # 5 mins
1329 if th.is_alive():
1330 print "WARNING: diff command timed out"
1331 p.terminate()
1332 th.join(5)
1333 if th.is_alive():
1334 p.kill()
1335 th.join()
1336
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001337 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001338 print "WARNING: failure running %s:\n%s\n" % (
1339 diff_program, "".join(err))
1340 self.patch = None
1341 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001342 diff = ptemp.read()
1343 finally:
1344 ptemp.close()
1345 stemp.close()
1346 ttemp.close()
1347
1348 self.patch = diff
1349 return self.tf, self.sf, self.patch
1350
1351
1352 def GetPatch(self):
1353 """Return a tuple (target_file, source_file, patch_data).
1354 patch_data may be None if ComputePatch hasn't been called, or if
1355 computing the patch failed."""
1356 return self.tf, self.sf, self.patch
1357
1358
1359def ComputeDifferences(diffs):
1360 """Call ComputePatch on all the Difference objects in 'diffs'."""
1361 print len(diffs), "diffs to compute"
1362
1363 # Do the largest files first, to try and reduce the long-pole effect.
1364 by_size = [(i.tf.size, i) for i in diffs]
1365 by_size.sort(reverse=True)
1366 by_size = [i[1] for i in by_size]
1367
1368 lock = threading.Lock()
1369 diff_iter = iter(by_size) # accessed under lock
1370
1371 def worker():
1372 try:
1373 lock.acquire()
1374 for d in diff_iter:
1375 lock.release()
1376 start = time.time()
1377 d.ComputePatch()
1378 dur = time.time() - start
1379 lock.acquire()
1380
1381 tf, sf, patch = d.GetPatch()
1382 if sf.name == tf.name:
1383 name = tf.name
1384 else:
1385 name = "%s (%s)" % (tf.name, sf.name)
1386 if patch is None:
1387 print "patching failed! %s" % (name,)
1388 else:
1389 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1390 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1391 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001392 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001393 print e
1394 raise
1395
1396 # start worker threads; wait for them all to finish.
1397 threads = [threading.Thread(target=worker)
1398 for i in range(OPTIONS.worker_threads)]
1399 for th in threads:
1400 th.start()
1401 while threads:
1402 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001403
1404
Dan Albert8b72aef2015-03-23 19:13:21 -07001405class BlockDifference(object):
1406 def __init__(self, partition, tgt, src=None, check_first_block=False,
1407 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001408 self.tgt = tgt
1409 self.src = src
1410 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001411 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001412
Tao Baodd2a5892015-03-12 12:32:37 -07001413 if version is None:
1414 version = 1
1415 if OPTIONS.info_dict:
1416 version = max(
1417 int(i) for i in
1418 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1419 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001420
1421 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001422 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001423 tmpdir = tempfile.mkdtemp()
1424 OPTIONS.tempfiles.append(tmpdir)
1425 self.path = os.path.join(tmpdir, partition)
1426 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001427 self._required_cache = b.max_stashed_size
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001428
Tao Baoaac4ad52015-10-16 15:26:34 -07001429 if src is None:
1430 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1431 else:
1432 _, self.device = GetTypeAndDevice("/" + partition,
1433 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001434
Tao Baod8d14be2016-02-04 14:26:02 -08001435 @property
1436 def required_cache(self):
1437 return self._required_cache
1438
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001439 def WriteScript(self, script, output_zip, progress=None):
1440 if not self.src:
1441 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001442 script.Print("Patching %s image unconditionally..." % (self.partition,))
1443 else:
1444 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001445
Dan Albert8b72aef2015-03-23 19:13:21 -07001446 if progress:
1447 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001448 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001449 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001450
Tao Bao9bc6bb22015-11-09 16:58:28 -08001451 def WriteStrictVerifyScript(self, script):
1452 """Verify all the blocks in the care_map, including clobbered blocks.
1453
1454 This differs from the WriteVerifyScript() function: a) it prints different
1455 error messages; b) it doesn't allow half-way updated images to pass the
1456 verification."""
1457
1458 partition = self.partition
1459 script.Print("Verifying %s..." % (partition,))
1460 ranges = self.tgt.care_map
1461 ranges_str = ranges.to_string_raw()
1462 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1463 'ui_print(" Verified.") || '
1464 'ui_print("\\"%s\\" has unexpected contents.");' % (
1465 self.device, ranges_str,
1466 self.tgt.TotalSha1(include_clobbered_blocks=True),
1467 self.device))
1468 script.AppendExtra("")
1469
Jesse Zhao75bcea02015-01-06 10:59:53 -08001470 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001471 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001472 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001473 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001474 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001475 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1476 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001477 if self.version >= 4:
1478 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1479 'block_image_verify("%s", '
1480 'package_extract_file("%s.transfer.list"), '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001481 '"%s.new.dat", "%s.patch.dat")) then') % (
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001482 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001483 self.device, partition, partition, partition))
1484 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001485 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1486 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001487 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001488 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001489 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001490 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001491 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001492 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001493 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001494 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001495 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001496
Tianjie Xufc3422a2015-12-15 11:53:59 -08001497 if self.version >= 4:
1498
1499 # Bug: 21124327
1500 # When generating incrementals for the system and vendor partitions in
1501 # version 4 or newer, explicitly check the first block (which contains
1502 # the superblock) of the partition to see if it's what we expect. If
1503 # this check fails, give an explicit log message about the partition
1504 # having been remounted R/W (the most likely explanation).
1505 if self.check_first_block:
1506 script.AppendExtra('check_first_block("%s");' % (self.device,))
1507
1508 # If version >= 4, try block recovery before abort update
1509 script.AppendExtra((
1510 'ifelse (block_image_recover("{device}", "{ranges}") && '
1511 'block_image_verify("{device}", '
1512 'package_extract_file("{partition}.transfer.list"), '
1513 '"{partition}.new.dat", "{partition}.patch.dat"), '
1514 'ui_print("{partition} recovered successfully."), '
1515 'abort("{partition} partition fails to recover"));\n'
1516 'endif;').format(device=self.device, ranges=ranges_str,
1517 partition=partition))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001518
Tao Baodd2a5892015-03-12 12:32:37 -07001519 # Abort the OTA update. Note that the incremental OTA cannot be applied
1520 # even if it may match the checksum of the target partition.
1521 # a) If version < 3, operations like move and erase will make changes
1522 # unconditionally and damage the partition.
1523 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001524 else:
1525 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1526 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001527
Tao Bao5fcaaef2015-06-01 13:40:49 -07001528 def _WritePostInstallVerifyScript(self, script):
1529 partition = self.partition
1530 script.Print('Verifying the updated %s image...' % (partition,))
1531 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1532 ranges = self.tgt.care_map
1533 ranges_str = ranges.to_string_raw()
1534 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1535 self.device, ranges_str,
1536 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001537
1538 # Bug: 20881595
1539 # Verify that extended blocks are really zeroed out.
1540 if self.tgt.extended:
1541 ranges_str = self.tgt.extended.to_string_raw()
1542 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1543 self.device, ranges_str,
1544 self._HashZeroBlocks(self.tgt.extended.size())))
1545 script.Print('Verified the updated %s image.' % (partition,))
1546 script.AppendExtra(
1547 'else\n'
1548 ' abort("%s partition has unexpected non-zero contents after OTA '
1549 'update");\n'
1550 'endif;' % (partition,))
1551 else:
1552 script.Print('Verified the updated %s image.' % (partition,))
1553
Tao Bao5fcaaef2015-06-01 13:40:49 -07001554 script.AppendExtra(
1555 'else\n'
1556 ' abort("%s partition has unexpected contents after OTA update");\n'
1557 'endif;' % (partition,))
1558
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001559 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001560 ZipWrite(output_zip,
1561 '{}.transfer.list'.format(self.path),
1562 '{}.transfer.list'.format(self.partition))
1563 ZipWrite(output_zip,
1564 '{}.new.dat'.format(self.path),
1565 '{}.new.dat'.format(self.partition))
1566 ZipWrite(output_zip,
1567 '{}.patch.dat'.format(self.path),
1568 '{}.patch.dat'.format(self.partition),
1569 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001570
Dan Albert8e0178d2015-01-27 15:53:15 -08001571 call = ('block_image_update("{device}", '
1572 'package_extract_file("{partition}.transfer.list"), '
1573 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1574 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001575 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001576
Dan Albert8b72aef2015-03-23 19:13:21 -07001577 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001578 data = source.ReadRangeSet(ranges)
1579 ctx = sha1()
1580
1581 for p in data:
1582 ctx.update(p)
1583
1584 return ctx.hexdigest()
1585
Tao Baoe9b61912015-07-09 17:37:49 -07001586 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1587 """Return the hash value for all zero blocks."""
1588 zero_block = '\x00' * 4096
1589 ctx = sha1()
1590 for _ in range(num_blocks):
1591 ctx.update(zero_block)
1592
1593 return ctx.hexdigest()
1594
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001595
1596DataImage = blockimgdiff.DataImage
1597
Doug Zongker96a57e72010-09-26 14:57:41 -07001598# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001599PARTITION_TYPES = {
1600 "yaffs2": "MTD",
1601 "mtd": "MTD",
1602 "ext4": "EMMC",
1603 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001604 "f2fs": "EMMC",
1605 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001606}
Doug Zongker96a57e72010-09-26 14:57:41 -07001607
1608def GetTypeAndDevice(mount_point, info):
1609 fstab = info["fstab"]
1610 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001611 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1612 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001613 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001614 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001615
1616
1617def ParseCertificate(data):
1618 """Parse a PEM-format certificate."""
1619 cert = []
1620 save = False
1621 for line in data.split("\n"):
1622 if "--END CERTIFICATE--" in line:
1623 break
1624 if save:
1625 cert.append(line)
1626 if "--BEGIN CERTIFICATE--" in line:
1627 save = True
1628 cert = "".join(cert).decode('base64')
1629 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001630
Doug Zongker412c02f2014-02-13 10:58:24 -08001631def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1632 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001633 """Generate a binary patch that creates the recovery image starting
1634 with the boot image. (Most of the space in these images is just the
1635 kernel, which is identical for the two, so the resulting patch
1636 should be efficient.) Add it to the output zip, along with a shell
1637 script that is run from init.rc on first boot to actually do the
1638 patching and install the new recovery image.
1639
1640 recovery_img and boot_img should be File objects for the
1641 corresponding images. info should be the dictionary returned by
1642 common.LoadInfoDict() on the input target_files.
1643 """
1644
Doug Zongker412c02f2014-02-13 10:58:24 -08001645 if info_dict is None:
1646 info_dict = OPTIONS.info_dict
1647
Tao Baof2cffbd2015-07-22 12:33:18 -07001648 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001649 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001650
Tao Baof2cffbd2015-07-22 12:33:18 -07001651 if full_recovery_image:
1652 output_sink("etc/recovery.img", recovery_img.data)
1653
1654 else:
1655 diff_program = ["imgdiff"]
1656 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1657 if os.path.exists(path):
1658 diff_program.append("-b")
1659 diff_program.append(path)
1660 bonus_args = "-b /system/etc/recovery-resource.dat"
1661 else:
1662 bonus_args = ""
1663
1664 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1665 _, _, patch = d.ComputePatch()
1666 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001667
Dan Albertebb19aa2015-03-27 19:11:53 -07001668 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001669 # The following GetTypeAndDevice()s need to use the path in the target
1670 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001671 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1672 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1673 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001674 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001675
Tao Baof2cffbd2015-07-22 12:33:18 -07001676 if full_recovery_image:
1677 sh = """#!/system/bin/sh
1678if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1679 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"
1680else
1681 log -t recovery "Recovery image already installed"
1682fi
1683""" % {'type': recovery_type,
1684 'device': recovery_device,
1685 'sha1': recovery_img.sha1,
1686 'size': recovery_img.size}
1687 else:
1688 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001689if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1690 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"
1691else
1692 log -t recovery "Recovery image already installed"
1693fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001694""" % {'boot_size': boot_img.size,
1695 'boot_sha1': boot_img.sha1,
1696 'recovery_size': recovery_img.size,
1697 'recovery_sha1': recovery_img.sha1,
1698 'boot_type': boot_type,
1699 'boot_device': boot_device,
1700 'recovery_type': recovery_type,
1701 'recovery_device': recovery_device,
1702 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001703
1704 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001705 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001706 # target-files expects it to be, and put it there.
1707 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001708 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001709 if system_root_image:
1710 init_rc_dir = os.path.join(input_dir, "ROOT")
1711 else:
1712 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001713 init_rc_files = os.listdir(init_rc_dir)
1714 for init_rc_file in init_rc_files:
1715 if (not init_rc_file.startswith('init.') or
1716 not init_rc_file.endswith('.rc')):
1717 continue
1718
1719 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001720 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001721 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001722 if m:
1723 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001724 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001725 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001726
1727 if found:
1728 break
1729
1730 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001731
1732 output_sink(sh_location, sh)