blob: 403c67da06fb7b2887ede785cd8b3a1e9ada7770 [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
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700417 if has_ramdisk:
418 cmd.extend(["--ramdisk", ramdisk_img.name])
419
Tao Baod95e9fd2015-03-29 23:07:41 -0700420 img_unsigned = None
421 if info_dict.get("vboot", None):
422 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700423 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700424 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700425 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700426
427 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700428 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700429 assert p.returncode == 0, "mkbootimg of %s image failed" % (
430 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700431
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100432 if (info_dict.get("boot_signer", None) == "true" and
433 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700434 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700435 cmd = [OPTIONS.boot_signer_path]
436 cmd.extend(OPTIONS.boot_signer_args)
437 cmd.extend([path, img.name,
438 info_dict["verity_key"] + ".pk8",
439 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700440 p = Run(cmd, stdout=subprocess.PIPE)
441 p.communicate()
442 assert p.returncode == 0, "boot_signer of %s image failed" % path
443
Tao Baod95e9fd2015-03-29 23:07:41 -0700444 # Sign the image if vboot is non-empty.
445 elif info_dict.get("vboot", None):
446 path = "/" + os.path.basename(sourcedir).lower()
447 img_keyblock = tempfile.NamedTemporaryFile()
448 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
449 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700450 info_dict["vboot_key"] + ".vbprivk",
451 info_dict["vboot_subkey"] + ".vbprivk",
452 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700453 img.name]
454 p = Run(cmd, stdout=subprocess.PIPE)
455 p.communicate()
456 assert p.returncode == 0, "vboot_signer of %s image failed" % path
457
Tao Baof3282b42015-04-01 11:21:55 -0700458 # Clean up the temp files.
459 img_unsigned.close()
460 img_keyblock.close()
461
Doug Zongkereef39442009-04-02 12:14:19 -0700462 img.seek(os.SEEK_SET, 0)
463 data = img.read()
464
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700465 if has_ramdisk:
466 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700467 img.close()
468
469 return data
470
471
Doug Zongkerd5131602012-08-02 14:46:42 -0700472def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
473 info_dict=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700474 """Return a File object with the desired bootable image.
475
476 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
477 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
478 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700479
Doug Zongker55d93282011-01-25 17:03:34 -0800480 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
481 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700482 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800483 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700484
485 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
486 if os.path.exists(prebuilt_path):
487 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
488 return File.FromLocalFile(name, prebuilt_path)
489
490 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700491
492 if info_dict is None:
493 info_dict = OPTIONS.info_dict
494
495 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800496 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
497 # for recovery.
498 has_ramdisk = (info_dict.get("system_root_image") != "true" or
499 prebuilt_name != "boot.img" or
500 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700501
Doug Zongker6f1d0312014-08-22 08:07:12 -0700502 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700503 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
504 os.path.join(unpack_dir, fs_config),
505 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700506 if data:
507 return File(name, data)
508 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800509
Doug Zongkereef39442009-04-02 12:14:19 -0700510
Doug Zongker75f17362009-12-08 13:46:44 -0800511def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800512 """Unzip the given archive into a temporary directory and return the name.
513
514 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
515 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
516
517 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
518 main file), open for reading.
519 """
Doug Zongkereef39442009-04-02 12:14:19 -0700520
521 tmp = tempfile.mkdtemp(prefix="targetfiles-")
522 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800523
524 def unzip_to_dir(filename, dirname):
525 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
526 if pattern is not None:
527 cmd.append(pattern)
528 p = Run(cmd, stdout=subprocess.PIPE)
529 p.communicate()
530 if p.returncode != 0:
531 raise ExternalError("failed to unzip input target-files \"%s\"" %
532 (filename,))
533
534 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
535 if m:
536 unzip_to_dir(m.group(1), tmp)
537 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
538 filename = m.group(1)
539 else:
540 unzip_to_dir(filename, tmp)
541
542 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700543
544
545def GetKeyPasswords(keylist):
546 """Given a list of keys, prompt the user to enter passwords for
547 those which require them. Return a {key: password} dict. password
548 will be None if the key has no password."""
549
Doug Zongker8ce7c252009-05-22 13:34:54 -0700550 no_passwords = []
551 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700552 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700553 devnull = open("/dev/null", "w+b")
554 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800555 # We don't need a password for things that aren't really keys.
556 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700557 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700558 continue
559
T.R. Fullhart37e10522013-03-18 10:31:26 -0700560 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700561 "-inform", "DER", "-nocrypt"],
562 stdin=devnull.fileno(),
563 stdout=devnull.fileno(),
564 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700565 p.communicate()
566 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700567 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700568 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700569 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700570 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
571 "-inform", "DER", "-passin", "pass:"],
572 stdin=devnull.fileno(),
573 stdout=devnull.fileno(),
574 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700575 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700576 if p.returncode == 0:
577 # Encrypted key with empty string as password.
578 key_passwords[k] = ''
579 elif stderr.startswith('Error decrypting key'):
580 # Definitely encrypted key.
581 # It would have said "Error reading key" if it didn't parse correctly.
582 need_passwords.append(k)
583 else:
584 # Potentially, a type of key that openssl doesn't understand.
585 # We'll let the routines in signapk.jar handle it.
586 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700587 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700588
T.R. Fullhart37e10522013-03-18 10:31:26 -0700589 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700590 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700591 return key_passwords
592
593
Alex Klyubin061289c2016-01-29 23:56:44 +0000594def SignFile(input_name, output_name, key, password, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700595 """Sign the input_name zip/jar/apk, producing output_name. Use the
596 given key and password (the latter may be None if the key does not
597 have a password.
598
Doug Zongker951495f2009-08-14 12:44:19 -0700599 If whole_file is true, use the "-w" option to SignApk to embed a
600 signature that covers the whole file in the archive comment of the
601 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700602 """
Doug Zongker951495f2009-08-14 12:44:19 -0700603
Alex Klyubin9667b182015-12-10 13:38:50 -0800604 java_library_path = os.path.join(
605 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
606
607 cmd = [OPTIONS.java_path, OPTIONS.java_args,
608 "-Djava.library.path=" + java_library_path,
609 "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700610 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
611 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700612 if whole_file:
613 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700614 cmd.extend([key + OPTIONS.public_key_suffix,
615 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800616 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700617
618 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700619 if password is not None:
620 password += "\n"
621 p.communicate(password)
622 if p.returncode != 0:
623 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
624
Doug Zongkereef39442009-04-02 12:14:19 -0700625
Doug Zongker37974732010-09-16 17:44:38 -0700626def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700627 """Check the data string passed against the max size limit, if
628 any, for the given target. Raise exception if the data is too big.
629 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700630
Dan Albert8b72aef2015-03-23 19:13:21 -0700631 if target.endswith(".img"):
632 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700633 mount_point = "/" + target
634
Ying Wangf8824af2014-06-03 14:07:27 -0700635 fs_type = None
636 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700637 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700638 if mount_point == "/userdata":
639 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700640 p = info_dict["fstab"][mount_point]
641 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800642 device = p.device
643 if "/" in device:
644 device = device[device.rfind("/")+1:]
645 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700646 if not fs_type or not limit:
647 return
Doug Zongkereef39442009-04-02 12:14:19 -0700648
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700649 if fs_type == "yaffs2":
650 # image size should be increased by 1/64th to account for the
651 # spare area (64 bytes per 2k page)
652 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800653 size = len(data)
654 pct = float(size) * 100.0 / limit
655 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
656 if pct >= 99.0:
657 raise ExternalError(msg)
658 elif pct >= 95.0:
659 print
660 print " WARNING: ", msg
661 print
662 elif OPTIONS.verbose:
663 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700664
665
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800666def ReadApkCerts(tf_zip):
667 """Given a target_files ZipFile, parse the META/apkcerts.txt file
668 and return a {package: cert} dict."""
669 certmap = {}
670 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
671 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700672 if not line:
673 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800674 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
675 r'private_key="(.*)"$', line)
676 if m:
677 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700678 public_key_suffix_len = len(OPTIONS.public_key_suffix)
679 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800680 if cert in SPECIAL_CERT_STRINGS and not privkey:
681 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700682 elif (cert.endswith(OPTIONS.public_key_suffix) and
683 privkey.endswith(OPTIONS.private_key_suffix) and
684 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
685 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800686 else:
687 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
688 return certmap
689
690
Doug Zongkereef39442009-04-02 12:14:19 -0700691COMMON_DOCSTRING = """
692 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700693 Prepend <dir>/bin to the list of places to search for binaries
694 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700695
Doug Zongker05d3dea2009-06-22 11:32:31 -0700696 -s (--device_specific) <file>
697 Path to the python module containing device-specific
698 releasetools code.
699
Doug Zongker8bec09e2009-11-30 15:37:14 -0800700 -x (--extra) <key=value>
701 Add a key/value pair to the 'extras' dict, which device-specific
702 extension code may look at.
703
Doug Zongkereef39442009-04-02 12:14:19 -0700704 -v (--verbose)
705 Show command lines being executed.
706
707 -h (--help)
708 Display this usage message and exit.
709"""
710
711def Usage(docstring):
712 print docstring.rstrip("\n")
713 print COMMON_DOCSTRING
714
715
716def ParseOptions(argv,
717 docstring,
718 extra_opts="", extra_long_opts=(),
719 extra_option_handler=None):
720 """Parse the options in argv and return any arguments that aren't
721 flags. docstring is the calling module's docstring, to be displayed
722 for errors and -h. extra_opts and extra_long_opts are for flags
723 defined by the caller, which are processed by passing them to
724 extra_option_handler."""
725
726 try:
727 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800728 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800729 ["help", "verbose", "path=", "signapk_path=",
730 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700731 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700732 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
733 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800734 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700735 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700736 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700737 Usage(docstring)
738 print "**", str(err), "**"
739 sys.exit(2)
740
Doug Zongkereef39442009-04-02 12:14:19 -0700741 for o, a in opts:
742 if o in ("-h", "--help"):
743 Usage(docstring)
744 sys.exit()
745 elif o in ("-v", "--verbose"):
746 OPTIONS.verbose = True
747 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700748 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700749 elif o in ("--signapk_path",):
750 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800751 elif o in ("--signapk_shared_library_path",):
752 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700753 elif o in ("--extra_signapk_args",):
754 OPTIONS.extra_signapk_args = shlex.split(a)
755 elif o in ("--java_path",):
756 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700757 elif o in ("--java_args",):
758 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700759 elif o in ("--public_key_suffix",):
760 OPTIONS.public_key_suffix = a
761 elif o in ("--private_key_suffix",):
762 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800763 elif o in ("--boot_signer_path",):
764 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700765 elif o in ("--boot_signer_args",):
766 OPTIONS.boot_signer_args = shlex.split(a)
767 elif o in ("--verity_signer_path",):
768 OPTIONS.verity_signer_path = a
769 elif o in ("--verity_signer_args",):
770 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700771 elif o in ("-s", "--device_specific"):
772 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800773 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800774 key, value = a.split("=", 1)
775 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700776 else:
777 if extra_option_handler is None or not extra_option_handler(o, a):
778 assert False, "unknown option \"%s\"" % (o,)
779
Doug Zongker85448772014-09-09 14:59:20 -0700780 if OPTIONS.search_path:
781 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
782 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700783
784 return args
785
786
Doug Zongkerfc44a512014-08-26 13:10:25 -0700787def MakeTempFile(prefix=None, suffix=None):
788 """Make a temp file and add it to the list of things to be deleted
789 when Cleanup() is called. Return the filename."""
790 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
791 os.close(fd)
792 OPTIONS.tempfiles.append(fn)
793 return fn
794
795
Doug Zongkereef39442009-04-02 12:14:19 -0700796def Cleanup():
797 for i in OPTIONS.tempfiles:
798 if os.path.isdir(i):
799 shutil.rmtree(i)
800 else:
801 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700802
803
804class PasswordManager(object):
805 def __init__(self):
806 self.editor = os.getenv("EDITOR", None)
807 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
808
809 def GetPasswords(self, items):
810 """Get passwords corresponding to each string in 'items',
811 returning a dict. (The dict may have keys in addition to the
812 values in 'items'.)
813
814 Uses the passwords in $ANDROID_PW_FILE if available, letting the
815 user edit that file to add more needed passwords. If no editor is
816 available, or $ANDROID_PW_FILE isn't define, prompts the user
817 interactively in the ordinary way.
818 """
819
820 current = self.ReadFile()
821
822 first = True
823 while True:
824 missing = []
825 for i in items:
826 if i not in current or not current[i]:
827 missing.append(i)
828 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700829 if not missing:
830 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700831
832 for i in missing:
833 current[i] = ""
834
835 if not first:
836 print "key file %s still missing some passwords." % (self.pwfile,)
837 answer = raw_input("try to edit again? [y]> ").strip()
838 if answer and answer[0] not in 'yY':
839 raise RuntimeError("key passwords unavailable")
840 first = False
841
842 current = self.UpdateAndReadFile(current)
843
Dan Albert8b72aef2015-03-23 19:13:21 -0700844 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700845 """Prompt the user to enter a value (password) for each key in
846 'current' whose value is fales. Returns a new dict with all the
847 values.
848 """
849 result = {}
850 for k, v in sorted(current.iteritems()):
851 if v:
852 result[k] = v
853 else:
854 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700855 result[k] = getpass.getpass(
856 "Enter password for %s key> " % k).strip()
857 if result[k]:
858 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700859 return result
860
861 def UpdateAndReadFile(self, current):
862 if not self.editor or not self.pwfile:
863 return self.PromptResult(current)
864
865 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700866 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700867 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
868 f.write("# (Additional spaces are harmless.)\n\n")
869
870 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700871 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
872 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700873 f.write("[[[ %s ]]] %s\n" % (v, k))
874 if not v and first_line is None:
875 # position cursor on first line with no password.
876 first_line = i + 4
877 f.close()
878
879 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
880 _, _ = p.communicate()
881
882 return self.ReadFile()
883
884 def ReadFile(self):
885 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700886 if self.pwfile is None:
887 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700888 try:
889 f = open(self.pwfile, "r")
890 for line in f:
891 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700892 if not line or line[0] == '#':
893 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700894 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
895 if not m:
896 print "failed to parse password file: ", line
897 else:
898 result[m.group(2)] = m.group(1)
899 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700900 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700901 if e.errno != errno.ENOENT:
902 print "error reading password file: ", str(e)
903 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700904
905
Dan Albert8e0178d2015-01-27 15:53:15 -0800906def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
907 compress_type=None):
908 import datetime
909
910 # http://b/18015246
911 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
912 # for files larger than 2GiB. We can work around this by adjusting their
913 # limit. Note that `zipfile.writestr()` will not work for strings larger than
914 # 2GiB. The Python interpreter sometimes rejects strings that large (though
915 # it isn't clear to me exactly what circumstances cause this).
916 # `zipfile.write()` must be used directly to work around this.
917 #
918 # This mess can be avoided if we port to python3.
919 saved_zip64_limit = zipfile.ZIP64_LIMIT
920 zipfile.ZIP64_LIMIT = (1 << 32) - 1
921
922 if compress_type is None:
923 compress_type = zip_file.compression
924 if arcname is None:
925 arcname = filename
926
927 saved_stat = os.stat(filename)
928
929 try:
930 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
931 # file to be zipped and reset it when we're done.
932 os.chmod(filename, perms)
933
934 # Use a fixed timestamp so the output is repeatable.
935 epoch = datetime.datetime.fromtimestamp(0)
936 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
937 os.utime(filename, (timestamp, timestamp))
938
939 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
940 finally:
941 os.chmod(filename, saved_stat.st_mode)
942 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
943 zipfile.ZIP64_LIMIT = saved_zip64_limit
944
945
Tao Bao58c1b962015-05-20 09:32:18 -0700946def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700947 compress_type=None):
948 """Wrap zipfile.writestr() function to work around the zip64 limit.
949
950 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
951 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
952 when calling crc32(bytes).
953
954 But it still works fine to write a shorter string into a large zip file.
955 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
956 when we know the string won't be too long.
957 """
958
959 saved_zip64_limit = zipfile.ZIP64_LIMIT
960 zipfile.ZIP64_LIMIT = (1 << 32) - 1
961
962 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
963 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700964 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700965 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700966 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800967 else:
Tao Baof3282b42015-04-01 11:21:55 -0700968 zinfo = zinfo_or_arcname
969
970 # If compress_type is given, it overrides the value in zinfo.
971 if compress_type is not None:
972 zinfo.compress_type = compress_type
973
Tao Bao58c1b962015-05-20 09:32:18 -0700974 # If perms is given, it has a priority.
975 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700976 # If perms doesn't set the file type, mark it as a regular file.
977 if perms & 0o770000 == 0:
978 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -0700979 zinfo.external_attr = perms << 16
980
Tao Baof3282b42015-04-01 11:21:55 -0700981 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700982 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
983
Dan Albert8b72aef2015-03-23 19:13:21 -0700984 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700985 zipfile.ZIP64_LIMIT = saved_zip64_limit
986
987
988def ZipClose(zip_file):
989 # http://b/18015246
990 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
991 # central directory.
992 saved_zip64_limit = zipfile.ZIP64_LIMIT
993 zipfile.ZIP64_LIMIT = (1 << 32) - 1
994
995 zip_file.close()
996
997 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700998
999
1000class DeviceSpecificParams(object):
1001 module = None
1002 def __init__(self, **kwargs):
1003 """Keyword arguments to the constructor become attributes of this
1004 object, which is passed to all functions in the device-specific
1005 module."""
1006 for k, v in kwargs.iteritems():
1007 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001008 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001009
1010 if self.module is None:
1011 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001012 if not path:
1013 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001014 try:
1015 if os.path.isdir(path):
1016 info = imp.find_module("releasetools", [path])
1017 else:
1018 d, f = os.path.split(path)
1019 b, x = os.path.splitext(f)
1020 if x == ".py":
1021 f = b
1022 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001023 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001024 self.module = imp.load_module("device_specific", *info)
1025 except ImportError:
1026 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001027
1028 def _DoCall(self, function_name, *args, **kwargs):
1029 """Call the named function in the device-specific module, passing
1030 the given args and kwargs. The first argument to the call will be
1031 the DeviceSpecific object itself. If there is no module, or the
1032 module does not define the function, return the value of the
1033 'default' kwarg (which itself defaults to None)."""
1034 if self.module is None or not hasattr(self.module, function_name):
1035 return kwargs.get("default", None)
1036 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1037
1038 def FullOTA_Assertions(self):
1039 """Called after emitting the block of assertions at the top of a
1040 full OTA package. Implementations can add whatever additional
1041 assertions they like."""
1042 return self._DoCall("FullOTA_Assertions")
1043
Doug Zongkere5ff5902012-01-17 10:55:37 -08001044 def FullOTA_InstallBegin(self):
1045 """Called at the start of full OTA installation."""
1046 return self._DoCall("FullOTA_InstallBegin")
1047
Doug Zongker05d3dea2009-06-22 11:32:31 -07001048 def FullOTA_InstallEnd(self):
1049 """Called at the end of full OTA installation; typically this is
1050 used to install the image for the device's baseband processor."""
1051 return self._DoCall("FullOTA_InstallEnd")
1052
1053 def IncrementalOTA_Assertions(self):
1054 """Called after emitting the block of assertions at the top of an
1055 incremental OTA package. Implementations can add whatever
1056 additional assertions they like."""
1057 return self._DoCall("IncrementalOTA_Assertions")
1058
Doug Zongkere5ff5902012-01-17 10:55:37 -08001059 def IncrementalOTA_VerifyBegin(self):
1060 """Called at the start of the verification phase of incremental
1061 OTA installation; additional checks can be placed here to abort
1062 the script before any changes are made."""
1063 return self._DoCall("IncrementalOTA_VerifyBegin")
1064
Doug Zongker05d3dea2009-06-22 11:32:31 -07001065 def IncrementalOTA_VerifyEnd(self):
1066 """Called at the end of the verification phase of incremental OTA
1067 installation; additional checks can be placed here to abort the
1068 script before any changes are made."""
1069 return self._DoCall("IncrementalOTA_VerifyEnd")
1070
Doug Zongkere5ff5902012-01-17 10:55:37 -08001071 def IncrementalOTA_InstallBegin(self):
1072 """Called at the start of incremental OTA installation (after
1073 verification is complete)."""
1074 return self._DoCall("IncrementalOTA_InstallBegin")
1075
Doug Zongker05d3dea2009-06-22 11:32:31 -07001076 def IncrementalOTA_InstallEnd(self):
1077 """Called at the end of incremental OTA installation; typically
1078 this is used to install the image for the device's baseband
1079 processor."""
1080 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001081
Tao Bao9bc6bb22015-11-09 16:58:28 -08001082 def VerifyOTA_Assertions(self):
1083 return self._DoCall("VerifyOTA_Assertions")
1084
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001085class File(object):
1086 def __init__(self, name, data):
1087 self.name = name
1088 self.data = data
1089 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001090 self.sha1 = sha1(data).hexdigest()
1091
1092 @classmethod
1093 def FromLocalFile(cls, name, diskname):
1094 f = open(diskname, "rb")
1095 data = f.read()
1096 f.close()
1097 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001098
1099 def WriteToTemp(self):
1100 t = tempfile.NamedTemporaryFile()
1101 t.write(self.data)
1102 t.flush()
1103 return t
1104
Geremy Condra36bd3652014-02-06 19:45:10 -08001105 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001106 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001107
1108DIFF_PROGRAM_BY_EXT = {
1109 ".gz" : "imgdiff",
1110 ".zip" : ["imgdiff", "-z"],
1111 ".jar" : ["imgdiff", "-z"],
1112 ".apk" : ["imgdiff", "-z"],
1113 ".img" : "imgdiff",
1114 }
1115
1116class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001117 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001118 self.tf = tf
1119 self.sf = sf
1120 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001121 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001122
1123 def ComputePatch(self):
1124 """Compute the patch (as a string of data) needed to turn sf into
1125 tf. Returns the same tuple as GetPatch()."""
1126
1127 tf = self.tf
1128 sf = self.sf
1129
Doug Zongker24cd2802012-08-14 16:36:15 -07001130 if self.diff_program:
1131 diff_program = self.diff_program
1132 else:
1133 ext = os.path.splitext(tf.name)[1]
1134 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001135
1136 ttemp = tf.WriteToTemp()
1137 stemp = sf.WriteToTemp()
1138
1139 ext = os.path.splitext(tf.name)[1]
1140
1141 try:
1142 ptemp = tempfile.NamedTemporaryFile()
1143 if isinstance(diff_program, list):
1144 cmd = copy.copy(diff_program)
1145 else:
1146 cmd = [diff_program]
1147 cmd.append(stemp.name)
1148 cmd.append(ttemp.name)
1149 cmd.append(ptemp.name)
1150 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001151 err = []
1152 def run():
1153 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001154 if e:
1155 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001156 th = threading.Thread(target=run)
1157 th.start()
1158 th.join(timeout=300) # 5 mins
1159 if th.is_alive():
1160 print "WARNING: diff command timed out"
1161 p.terminate()
1162 th.join(5)
1163 if th.is_alive():
1164 p.kill()
1165 th.join()
1166
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001167 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001168 print "WARNING: failure running %s:\n%s\n" % (
1169 diff_program, "".join(err))
1170 self.patch = None
1171 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001172 diff = ptemp.read()
1173 finally:
1174 ptemp.close()
1175 stemp.close()
1176 ttemp.close()
1177
1178 self.patch = diff
1179 return self.tf, self.sf, self.patch
1180
1181
1182 def GetPatch(self):
1183 """Return a tuple (target_file, source_file, patch_data).
1184 patch_data may be None if ComputePatch hasn't been called, or if
1185 computing the patch failed."""
1186 return self.tf, self.sf, self.patch
1187
1188
1189def ComputeDifferences(diffs):
1190 """Call ComputePatch on all the Difference objects in 'diffs'."""
1191 print len(diffs), "diffs to compute"
1192
1193 # Do the largest files first, to try and reduce the long-pole effect.
1194 by_size = [(i.tf.size, i) for i in diffs]
1195 by_size.sort(reverse=True)
1196 by_size = [i[1] for i in by_size]
1197
1198 lock = threading.Lock()
1199 diff_iter = iter(by_size) # accessed under lock
1200
1201 def worker():
1202 try:
1203 lock.acquire()
1204 for d in diff_iter:
1205 lock.release()
1206 start = time.time()
1207 d.ComputePatch()
1208 dur = time.time() - start
1209 lock.acquire()
1210
1211 tf, sf, patch = d.GetPatch()
1212 if sf.name == tf.name:
1213 name = tf.name
1214 else:
1215 name = "%s (%s)" % (tf.name, sf.name)
1216 if patch is None:
1217 print "patching failed! %s" % (name,)
1218 else:
1219 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1220 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1221 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001222 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001223 print e
1224 raise
1225
1226 # start worker threads; wait for them all to finish.
1227 threads = [threading.Thread(target=worker)
1228 for i in range(OPTIONS.worker_threads)]
1229 for th in threads:
1230 th.start()
1231 while threads:
1232 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001233
1234
Dan Albert8b72aef2015-03-23 19:13:21 -07001235class BlockDifference(object):
1236 def __init__(self, partition, tgt, src=None, check_first_block=False,
1237 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001238 self.tgt = tgt
1239 self.src = src
1240 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001241 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001242
Tao Baodd2a5892015-03-12 12:32:37 -07001243 if version is None:
1244 version = 1
1245 if OPTIONS.info_dict:
1246 version = max(
1247 int(i) for i in
1248 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1249 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001250
1251 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001252 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001253 tmpdir = tempfile.mkdtemp()
1254 OPTIONS.tempfiles.append(tmpdir)
1255 self.path = os.path.join(tmpdir, partition)
1256 b.Compute(self.path)
Tao Baob4cfca52016-02-04 14:26:02 -08001257 self._required_cache = b.max_stashed_size
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001258
Tao Baoaac4ad52015-10-16 15:26:34 -07001259 if src is None:
1260 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1261 else:
1262 _, self.device = GetTypeAndDevice("/" + partition,
1263 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001264
Tao Baob4cfca52016-02-04 14:26:02 -08001265 @property
1266 def required_cache(self):
1267 return self._required_cache
1268
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001269 def WriteScript(self, script, output_zip, progress=None):
1270 if not self.src:
1271 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001272 script.Print("Patching %s image unconditionally..." % (self.partition,))
1273 else:
1274 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001275
Dan Albert8b72aef2015-03-23 19:13:21 -07001276 if progress:
1277 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001278 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001279 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001280
Tao Bao9bc6bb22015-11-09 16:58:28 -08001281 def WriteStrictVerifyScript(self, script):
1282 """Verify all the blocks in the care_map, including clobbered blocks.
1283
1284 This differs from the WriteVerifyScript() function: a) it prints different
1285 error messages; b) it doesn't allow half-way updated images to pass the
1286 verification."""
1287
1288 partition = self.partition
1289 script.Print("Verifying %s..." % (partition,))
1290 ranges = self.tgt.care_map
1291 ranges_str = ranges.to_string_raw()
1292 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1293 'ui_print(" Verified.") || '
1294 'ui_print("\\"%s\\" has unexpected contents.");' % (
1295 self.device, ranges_str,
1296 self.tgt.TotalSha1(include_clobbered_blocks=True),
1297 self.device))
1298 script.AppendExtra("")
1299
Jesse Zhao75bcea02015-01-06 10:59:53 -08001300 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001301 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001302 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001303 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001304 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001305 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1306 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001307 if self.version >= 4:
1308 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1309 'block_image_verify("%s", '
1310 'package_extract_file("%s.transfer.list"), '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001311 '"%s.new.dat", "%s.patch.dat")) then') % (
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001312 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001313 self.device, partition, partition, partition))
1314 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001315 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1316 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001317 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001318 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001319 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001320 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001321 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001322 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001323 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001324 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001325 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001326
Tianjie Xufc3422a2015-12-15 11:53:59 -08001327 if self.version >= 4:
1328
1329 # Bug: 21124327
1330 # When generating incrementals for the system and vendor partitions in
1331 # version 4 or newer, explicitly check the first block (which contains
1332 # the superblock) of the partition to see if it's what we expect. If
1333 # this check fails, give an explicit log message about the partition
1334 # having been remounted R/W (the most likely explanation).
1335 if self.check_first_block:
1336 script.AppendExtra('check_first_block("%s");' % (self.device,))
1337
1338 # If version >= 4, try block recovery before abort update
1339 script.AppendExtra((
1340 'ifelse (block_image_recover("{device}", "{ranges}") && '
1341 'block_image_verify("{device}", '
1342 'package_extract_file("{partition}.transfer.list"), '
1343 '"{partition}.new.dat", "{partition}.patch.dat"), '
1344 'ui_print("{partition} recovered successfully."), '
1345 'abort("{partition} partition fails to recover"));\n'
1346 'endif;').format(device=self.device, ranges=ranges_str,
1347 partition=partition))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001348
Tao Baodd2a5892015-03-12 12:32:37 -07001349 # Abort the OTA update. Note that the incremental OTA cannot be applied
1350 # even if it may match the checksum of the target partition.
1351 # a) If version < 3, operations like move and erase will make changes
1352 # unconditionally and damage the partition.
1353 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001354 else:
1355 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1356 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001357
Tao Bao5fcaaef2015-06-01 13:40:49 -07001358 def _WritePostInstallVerifyScript(self, script):
1359 partition = self.partition
1360 script.Print('Verifying the updated %s image...' % (partition,))
1361 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1362 ranges = self.tgt.care_map
1363 ranges_str = ranges.to_string_raw()
1364 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1365 self.device, ranges_str,
1366 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001367
1368 # Bug: 20881595
1369 # Verify that extended blocks are really zeroed out.
1370 if self.tgt.extended:
1371 ranges_str = self.tgt.extended.to_string_raw()
1372 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1373 self.device, ranges_str,
1374 self._HashZeroBlocks(self.tgt.extended.size())))
1375 script.Print('Verified the updated %s image.' % (partition,))
1376 script.AppendExtra(
1377 'else\n'
1378 ' abort("%s partition has unexpected non-zero contents after OTA '
1379 'update");\n'
1380 'endif;' % (partition,))
1381 else:
1382 script.Print('Verified the updated %s image.' % (partition,))
1383
Tao Bao5fcaaef2015-06-01 13:40:49 -07001384 script.AppendExtra(
1385 'else\n'
1386 ' abort("%s partition has unexpected contents after OTA update");\n'
1387 'endif;' % (partition,))
1388
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001389 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001390 ZipWrite(output_zip,
1391 '{}.transfer.list'.format(self.path),
1392 '{}.transfer.list'.format(self.partition))
1393 ZipWrite(output_zip,
1394 '{}.new.dat'.format(self.path),
1395 '{}.new.dat'.format(self.partition))
1396 ZipWrite(output_zip,
1397 '{}.patch.dat'.format(self.path),
1398 '{}.patch.dat'.format(self.partition),
1399 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001400
Dan Albert8e0178d2015-01-27 15:53:15 -08001401 call = ('block_image_update("{device}", '
1402 'package_extract_file("{partition}.transfer.list"), '
1403 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1404 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001405 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001406
Dan Albert8b72aef2015-03-23 19:13:21 -07001407 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001408 data = source.ReadRangeSet(ranges)
1409 ctx = sha1()
1410
1411 for p in data:
1412 ctx.update(p)
1413
1414 return ctx.hexdigest()
1415
Tao Baoe9b61912015-07-09 17:37:49 -07001416 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1417 """Return the hash value for all zero blocks."""
1418 zero_block = '\x00' * 4096
1419 ctx = sha1()
1420 for _ in range(num_blocks):
1421 ctx.update(zero_block)
1422
1423 return ctx.hexdigest()
1424
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001425
1426DataImage = blockimgdiff.DataImage
1427
Doug Zongker96a57e72010-09-26 14:57:41 -07001428# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001429PARTITION_TYPES = {
1430 "yaffs2": "MTD",
1431 "mtd": "MTD",
1432 "ext4": "EMMC",
1433 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001434 "f2fs": "EMMC",
1435 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001436}
Doug Zongker96a57e72010-09-26 14:57:41 -07001437
1438def GetTypeAndDevice(mount_point, info):
1439 fstab = info["fstab"]
1440 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001441 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1442 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001443 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001444 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001445
1446
1447def ParseCertificate(data):
1448 """Parse a PEM-format certificate."""
1449 cert = []
1450 save = False
1451 for line in data.split("\n"):
1452 if "--END CERTIFICATE--" in line:
1453 break
1454 if save:
1455 cert.append(line)
1456 if "--BEGIN CERTIFICATE--" in line:
1457 save = True
1458 cert = "".join(cert).decode('base64')
1459 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001460
Doug Zongker412c02f2014-02-13 10:58:24 -08001461def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1462 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001463 """Generate a binary patch that creates the recovery image starting
1464 with the boot image. (Most of the space in these images is just the
1465 kernel, which is identical for the two, so the resulting patch
1466 should be efficient.) Add it to the output zip, along with a shell
1467 script that is run from init.rc on first boot to actually do the
1468 patching and install the new recovery image.
1469
1470 recovery_img and boot_img should be File objects for the
1471 corresponding images. info should be the dictionary returned by
1472 common.LoadInfoDict() on the input target_files.
1473 """
1474
Doug Zongker412c02f2014-02-13 10:58:24 -08001475 if info_dict is None:
1476 info_dict = OPTIONS.info_dict
1477
Tao Baof2cffbd2015-07-22 12:33:18 -07001478 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001479 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001480
Tao Baof2cffbd2015-07-22 12:33:18 -07001481 if full_recovery_image:
1482 output_sink("etc/recovery.img", recovery_img.data)
1483
1484 else:
1485 diff_program = ["imgdiff"]
1486 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1487 if os.path.exists(path):
1488 diff_program.append("-b")
1489 diff_program.append(path)
1490 bonus_args = "-b /system/etc/recovery-resource.dat"
1491 else:
1492 bonus_args = ""
1493
1494 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1495 _, _, patch = d.ComputePatch()
1496 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001497
Dan Albertebb19aa2015-03-27 19:11:53 -07001498 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001499 # The following GetTypeAndDevice()s need to use the path in the target
1500 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001501 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1502 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1503 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001504 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001505
Tao Baof2cffbd2015-07-22 12:33:18 -07001506 if full_recovery_image:
1507 sh = """#!/system/bin/sh
1508if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1509 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"
1510else
1511 log -t recovery "Recovery image already installed"
1512fi
1513""" % {'type': recovery_type,
1514 'device': recovery_device,
1515 'sha1': recovery_img.sha1,
1516 'size': recovery_img.size}
1517 else:
1518 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001519if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1520 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"
1521else
1522 log -t recovery "Recovery image already installed"
1523fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001524""" % {'boot_size': boot_img.size,
1525 'boot_sha1': boot_img.sha1,
1526 'recovery_size': recovery_img.size,
1527 'recovery_sha1': recovery_img.sha1,
1528 'boot_type': boot_type,
1529 'boot_device': boot_device,
1530 'recovery_type': recovery_type,
1531 'recovery_device': recovery_device,
1532 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001533
1534 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001535 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001536 # target-files expects it to be, and put it there.
1537 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001538 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001539 if system_root_image:
1540 init_rc_dir = os.path.join(input_dir, "ROOT")
1541 else:
1542 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001543 init_rc_files = os.listdir(init_rc_dir)
1544 for init_rc_file in init_rc_files:
1545 if (not init_rc_file.startswith('init.') or
1546 not init_rc_file.endswith('.rc')):
1547 continue
1548
1549 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001550 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001551 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001552 if m:
1553 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001554 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001555 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001556
1557 if found:
1558 break
1559
1560 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001561
1562 output_sink(sh_location, sh)