blob: 2c8bbc610e706cf90698e6219e228544ca8b313a [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
Dan Albert8b72aef2015-03-23 19:13:21 -070033import rangelib
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Tao Baof3282b42015-04-01 11:21:55 -070035from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036
Doug Zongkereef39442009-04-02 12:14:19 -070037
Dan Albert8b72aef2015-03-23 19:13:21 -070038class Options(object):
39 def __init__(self):
40 platform_search_path = {
41 "linux2": "out/host/linux-x86",
42 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070043 }
Doug Zongker85448772014-09-09 14:59:20 -070044
Dan Albert8b72aef2015-03-23 19:13:21 -070045 self.search_path = platform_search_path.get(sys.platform, None)
46 self.signapk_path = "framework/signapk.jar" # Relative to search_path
47 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.
496 has_ramdisk = (info_dict.get("system_root_image", None) != "true" or
497 prebuilt_name != "boot.img")
498
Doug Zongker6f1d0312014-08-22 08:07:12 -0700499 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700500 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
501 os.path.join(unpack_dir, fs_config),
502 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700503 if data:
504 return File(name, data)
505 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800506
Doug Zongkereef39442009-04-02 12:14:19 -0700507
Doug Zongker75f17362009-12-08 13:46:44 -0800508def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800509 """Unzip the given archive into a temporary directory and return the name.
510
511 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
512 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
513
514 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
515 main file), open for reading.
516 """
Doug Zongkereef39442009-04-02 12:14:19 -0700517
518 tmp = tempfile.mkdtemp(prefix="targetfiles-")
519 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800520
521 def unzip_to_dir(filename, dirname):
522 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
523 if pattern is not None:
524 cmd.append(pattern)
525 p = Run(cmd, stdout=subprocess.PIPE)
526 p.communicate()
527 if p.returncode != 0:
528 raise ExternalError("failed to unzip input target-files \"%s\"" %
529 (filename,))
530
531 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
532 if m:
533 unzip_to_dir(m.group(1), tmp)
534 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
535 filename = m.group(1)
536 else:
537 unzip_to_dir(filename, tmp)
538
539 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700540
541
542def GetKeyPasswords(keylist):
543 """Given a list of keys, prompt the user to enter passwords for
544 those which require them. Return a {key: password} dict. password
545 will be None if the key has no password."""
546
Doug Zongker8ce7c252009-05-22 13:34:54 -0700547 no_passwords = []
548 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700549 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700550 devnull = open("/dev/null", "w+b")
551 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800552 # We don't need a password for things that aren't really keys.
553 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700554 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700555 continue
556
T.R. Fullhart37e10522013-03-18 10:31:26 -0700557 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700558 "-inform", "DER", "-nocrypt"],
559 stdin=devnull.fileno(),
560 stdout=devnull.fileno(),
561 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700562 p.communicate()
563 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700564 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700565 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700566 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700567 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
568 "-inform", "DER", "-passin", "pass:"],
569 stdin=devnull.fileno(),
570 stdout=devnull.fileno(),
571 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700572 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700573 if p.returncode == 0:
574 # Encrypted key with empty string as password.
575 key_passwords[k] = ''
576 elif stderr.startswith('Error decrypting key'):
577 # Definitely encrypted key.
578 # It would have said "Error reading key" if it didn't parse correctly.
579 need_passwords.append(k)
580 else:
581 # Potentially, a type of key that openssl doesn't understand.
582 # We'll let the routines in signapk.jar handle it.
583 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700584 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700585
T.R. Fullhart37e10522013-03-18 10:31:26 -0700586 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700587 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700588 return key_passwords
589
590
Doug Zongker951495f2009-08-14 12:44:19 -0700591def SignFile(input_name, output_name, key, password, align=None,
592 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700593 """Sign the input_name zip/jar/apk, producing output_name. Use the
594 given key and password (the latter may be None if the key does not
595 have a password.
596
597 If align is an integer > 1, zipalign is run to align stored files in
598 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700599
600 If whole_file is true, use the "-w" option to SignApk to embed a
601 signature that covers the whole file in the archive comment of the
602 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700603 """
Doug Zongker951495f2009-08-14 12:44:19 -0700604
Doug Zongkereef39442009-04-02 12:14:19 -0700605 if align == 0 or align == 1:
606 align = None
607
608 if align:
609 temp = tempfile.NamedTemporaryFile()
610 sign_name = temp.name
611 else:
612 sign_name = output_name
613
Baligh Uddin339ee492014-09-05 11:18:07 -0700614 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700615 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
616 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700617 if whole_file:
618 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700619 cmd.extend([key + OPTIONS.public_key_suffix,
620 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700621 input_name, sign_name])
622
623 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700624 if password is not None:
625 password += "\n"
626 p.communicate(password)
627 if p.returncode != 0:
628 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
629
630 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700631 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700632 p.communicate()
633 if p.returncode != 0:
634 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
635 temp.close()
636
637
Doug Zongker37974732010-09-16 17:44:38 -0700638def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700639 """Check the data string passed against the max size limit, if
640 any, for the given target. Raise exception if the data is too big.
641 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700642
Dan Albert8b72aef2015-03-23 19:13:21 -0700643 if target.endswith(".img"):
644 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700645 mount_point = "/" + target
646
Ying Wangf8824af2014-06-03 14:07:27 -0700647 fs_type = None
648 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700649 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700650 if mount_point == "/userdata":
651 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700652 p = info_dict["fstab"][mount_point]
653 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800654 device = p.device
655 if "/" in device:
656 device = device[device.rfind("/")+1:]
657 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700658 if not fs_type or not limit:
659 return
Doug Zongkereef39442009-04-02 12:14:19 -0700660
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700661 if fs_type == "yaffs2":
662 # image size should be increased by 1/64th to account for the
663 # spare area (64 bytes per 2k page)
664 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800665 size = len(data)
666 pct = float(size) * 100.0 / limit
667 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
668 if pct >= 99.0:
669 raise ExternalError(msg)
670 elif pct >= 95.0:
671 print
672 print " WARNING: ", msg
673 print
674 elif OPTIONS.verbose:
675 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700676
677
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800678def ReadApkCerts(tf_zip):
679 """Given a target_files ZipFile, parse the META/apkcerts.txt file
680 and return a {package: cert} dict."""
681 certmap = {}
682 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
683 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700684 if not line:
685 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800686 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
687 r'private_key="(.*)"$', line)
688 if m:
689 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700690 public_key_suffix_len = len(OPTIONS.public_key_suffix)
691 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800692 if cert in SPECIAL_CERT_STRINGS and not privkey:
693 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700694 elif (cert.endswith(OPTIONS.public_key_suffix) and
695 privkey.endswith(OPTIONS.private_key_suffix) and
696 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
697 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800698 else:
699 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
700 return certmap
701
702
Doug Zongkereef39442009-04-02 12:14:19 -0700703COMMON_DOCSTRING = """
704 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700705 Prepend <dir>/bin to the list of places to search for binaries
706 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700707
Doug Zongker05d3dea2009-06-22 11:32:31 -0700708 -s (--device_specific) <file>
709 Path to the python module containing device-specific
710 releasetools code.
711
Doug Zongker8bec09e2009-11-30 15:37:14 -0800712 -x (--extra) <key=value>
713 Add a key/value pair to the 'extras' dict, which device-specific
714 extension code may look at.
715
Doug Zongkereef39442009-04-02 12:14:19 -0700716 -v (--verbose)
717 Show command lines being executed.
718
719 -h (--help)
720 Display this usage message and exit.
721"""
722
723def Usage(docstring):
724 print docstring.rstrip("\n")
725 print COMMON_DOCSTRING
726
727
728def ParseOptions(argv,
729 docstring,
730 extra_opts="", extra_long_opts=(),
731 extra_option_handler=None):
732 """Parse the options in argv and return any arguments that aren't
733 flags. docstring is the calling module's docstring, to be displayed
734 for errors and -h. extra_opts and extra_long_opts are for flags
735 defined by the caller, which are processed by passing them to
736 extra_option_handler."""
737
738 try:
739 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800740 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700741 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700742 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700743 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
744 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800745 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700746 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700747 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700748 Usage(docstring)
749 print "**", str(err), "**"
750 sys.exit(2)
751
Doug Zongkereef39442009-04-02 12:14:19 -0700752 for o, a in opts:
753 if o in ("-h", "--help"):
754 Usage(docstring)
755 sys.exit()
756 elif o in ("-v", "--verbose"):
757 OPTIONS.verbose = True
758 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700759 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700760 elif o in ("--signapk_path",):
761 OPTIONS.signapk_path = a
762 elif o in ("--extra_signapk_args",):
763 OPTIONS.extra_signapk_args = shlex.split(a)
764 elif o in ("--java_path",):
765 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700766 elif o in ("--java_args",):
767 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700768 elif o in ("--public_key_suffix",):
769 OPTIONS.public_key_suffix = a
770 elif o in ("--private_key_suffix",):
771 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800772 elif o in ("--boot_signer_path",):
773 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700774 elif o in ("--boot_signer_args",):
775 OPTIONS.boot_signer_args = shlex.split(a)
776 elif o in ("--verity_signer_path",):
777 OPTIONS.verity_signer_path = a
778 elif o in ("--verity_signer_args",):
779 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700780 elif o in ("-s", "--device_specific"):
781 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800782 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800783 key, value = a.split("=", 1)
784 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700785 else:
786 if extra_option_handler is None or not extra_option_handler(o, a):
787 assert False, "unknown option \"%s\"" % (o,)
788
Doug Zongker85448772014-09-09 14:59:20 -0700789 if OPTIONS.search_path:
790 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
791 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700792
793 return args
794
795
Doug Zongkerfc44a512014-08-26 13:10:25 -0700796def MakeTempFile(prefix=None, suffix=None):
797 """Make a temp file and add it to the list of things to be deleted
798 when Cleanup() is called. Return the filename."""
799 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
800 os.close(fd)
801 OPTIONS.tempfiles.append(fn)
802 return fn
803
804
Doug Zongkereef39442009-04-02 12:14:19 -0700805def Cleanup():
806 for i in OPTIONS.tempfiles:
807 if os.path.isdir(i):
808 shutil.rmtree(i)
809 else:
810 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700811
812
813class PasswordManager(object):
814 def __init__(self):
815 self.editor = os.getenv("EDITOR", None)
816 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
817
818 def GetPasswords(self, items):
819 """Get passwords corresponding to each string in 'items',
820 returning a dict. (The dict may have keys in addition to the
821 values in 'items'.)
822
823 Uses the passwords in $ANDROID_PW_FILE if available, letting the
824 user edit that file to add more needed passwords. If no editor is
825 available, or $ANDROID_PW_FILE isn't define, prompts the user
826 interactively in the ordinary way.
827 """
828
829 current = self.ReadFile()
830
831 first = True
832 while True:
833 missing = []
834 for i in items:
835 if i not in current or not current[i]:
836 missing.append(i)
837 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700838 if not missing:
839 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700840
841 for i in missing:
842 current[i] = ""
843
844 if not first:
845 print "key file %s still missing some passwords." % (self.pwfile,)
846 answer = raw_input("try to edit again? [y]> ").strip()
847 if answer and answer[0] not in 'yY':
848 raise RuntimeError("key passwords unavailable")
849 first = False
850
851 current = self.UpdateAndReadFile(current)
852
Dan Albert8b72aef2015-03-23 19:13:21 -0700853 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700854 """Prompt the user to enter a value (password) for each key in
855 'current' whose value is fales. Returns a new dict with all the
856 values.
857 """
858 result = {}
859 for k, v in sorted(current.iteritems()):
860 if v:
861 result[k] = v
862 else:
863 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700864 result[k] = getpass.getpass(
865 "Enter password for %s key> " % k).strip()
866 if result[k]:
867 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700868 return result
869
870 def UpdateAndReadFile(self, current):
871 if not self.editor or not self.pwfile:
872 return self.PromptResult(current)
873
874 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700875 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700876 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
877 f.write("# (Additional spaces are harmless.)\n\n")
878
879 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700880 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
881 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700882 f.write("[[[ %s ]]] %s\n" % (v, k))
883 if not v and first_line is None:
884 # position cursor on first line with no password.
885 first_line = i + 4
886 f.close()
887
888 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
889 _, _ = p.communicate()
890
891 return self.ReadFile()
892
893 def ReadFile(self):
894 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700895 if self.pwfile is None:
896 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700897 try:
898 f = open(self.pwfile, "r")
899 for line in f:
900 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700901 if not line or line[0] == '#':
902 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700903 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
904 if not m:
905 print "failed to parse password file: ", line
906 else:
907 result[m.group(2)] = m.group(1)
908 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700909 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700910 if e.errno != errno.ENOENT:
911 print "error reading password file: ", str(e)
912 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700913
914
Dan Albert8e0178d2015-01-27 15:53:15 -0800915def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
916 compress_type=None):
917 import datetime
918
919 # http://b/18015246
920 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
921 # for files larger than 2GiB. We can work around this by adjusting their
922 # limit. Note that `zipfile.writestr()` will not work for strings larger than
923 # 2GiB. The Python interpreter sometimes rejects strings that large (though
924 # it isn't clear to me exactly what circumstances cause this).
925 # `zipfile.write()` must be used directly to work around this.
926 #
927 # This mess can be avoided if we port to python3.
928 saved_zip64_limit = zipfile.ZIP64_LIMIT
929 zipfile.ZIP64_LIMIT = (1 << 32) - 1
930
931 if compress_type is None:
932 compress_type = zip_file.compression
933 if arcname is None:
934 arcname = filename
935
936 saved_stat = os.stat(filename)
937
938 try:
939 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
940 # file to be zipped and reset it when we're done.
941 os.chmod(filename, perms)
942
943 # Use a fixed timestamp so the output is repeatable.
944 epoch = datetime.datetime.fromtimestamp(0)
945 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
946 os.utime(filename, (timestamp, timestamp))
947
948 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
949 finally:
950 os.chmod(filename, saved_stat.st_mode)
951 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
952 zipfile.ZIP64_LIMIT = saved_zip64_limit
953
954
Tao Bao58c1b962015-05-20 09:32:18 -0700955def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700956 compress_type=None):
957 """Wrap zipfile.writestr() function to work around the zip64 limit.
958
959 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
960 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
961 when calling crc32(bytes).
962
963 But it still works fine to write a shorter string into a large zip file.
964 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
965 when we know the string won't be too long.
966 """
967
968 saved_zip64_limit = zipfile.ZIP64_LIMIT
969 zipfile.ZIP64_LIMIT = (1 << 32) - 1
970
971 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
972 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700973 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700974 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700975 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800976 else:
Tao Baof3282b42015-04-01 11:21:55 -0700977 zinfo = zinfo_or_arcname
978
979 # If compress_type is given, it overrides the value in zinfo.
980 if compress_type is not None:
981 zinfo.compress_type = compress_type
982
Tao Bao58c1b962015-05-20 09:32:18 -0700983 # If perms is given, it has a priority.
984 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700985 # If perms doesn't set the file type, mark it as a regular file.
986 if perms & 0o770000 == 0:
987 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -0700988 zinfo.external_attr = perms << 16
989
Tao Baof3282b42015-04-01 11:21:55 -0700990 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700991 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
992
Dan Albert8b72aef2015-03-23 19:13:21 -0700993 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700994 zipfile.ZIP64_LIMIT = saved_zip64_limit
995
996
997def ZipClose(zip_file):
998 # http://b/18015246
999 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1000 # central directory.
1001 saved_zip64_limit = zipfile.ZIP64_LIMIT
1002 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1003
1004 zip_file.close()
1005
1006 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001007
1008
1009class DeviceSpecificParams(object):
1010 module = None
1011 def __init__(self, **kwargs):
1012 """Keyword arguments to the constructor become attributes of this
1013 object, which is passed to all functions in the device-specific
1014 module."""
1015 for k, v in kwargs.iteritems():
1016 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001017 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001018
1019 if self.module is None:
1020 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001021 if not path:
1022 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001023 try:
1024 if os.path.isdir(path):
1025 info = imp.find_module("releasetools", [path])
1026 else:
1027 d, f = os.path.split(path)
1028 b, x = os.path.splitext(f)
1029 if x == ".py":
1030 f = b
1031 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001032 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001033 self.module = imp.load_module("device_specific", *info)
1034 except ImportError:
1035 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001036
1037 def _DoCall(self, function_name, *args, **kwargs):
1038 """Call the named function in the device-specific module, passing
1039 the given args and kwargs. The first argument to the call will be
1040 the DeviceSpecific object itself. If there is no module, or the
1041 module does not define the function, return the value of the
1042 'default' kwarg (which itself defaults to None)."""
1043 if self.module is None or not hasattr(self.module, function_name):
1044 return kwargs.get("default", None)
1045 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1046
1047 def FullOTA_Assertions(self):
1048 """Called after emitting the block of assertions at the top of a
1049 full OTA package. Implementations can add whatever additional
1050 assertions they like."""
1051 return self._DoCall("FullOTA_Assertions")
1052
Doug Zongkere5ff5902012-01-17 10:55:37 -08001053 def FullOTA_InstallBegin(self):
1054 """Called at the start of full OTA installation."""
1055 return self._DoCall("FullOTA_InstallBegin")
1056
Doug Zongker05d3dea2009-06-22 11:32:31 -07001057 def FullOTA_InstallEnd(self):
1058 """Called at the end of full OTA installation; typically this is
1059 used to install the image for the device's baseband processor."""
1060 return self._DoCall("FullOTA_InstallEnd")
1061
1062 def IncrementalOTA_Assertions(self):
1063 """Called after emitting the block of assertions at the top of an
1064 incremental OTA package. Implementations can add whatever
1065 additional assertions they like."""
1066 return self._DoCall("IncrementalOTA_Assertions")
1067
Doug Zongkere5ff5902012-01-17 10:55:37 -08001068 def IncrementalOTA_VerifyBegin(self):
1069 """Called at the start of the verification phase of incremental
1070 OTA installation; additional checks can be placed here to abort
1071 the script before any changes are made."""
1072 return self._DoCall("IncrementalOTA_VerifyBegin")
1073
Doug Zongker05d3dea2009-06-22 11:32:31 -07001074 def IncrementalOTA_VerifyEnd(self):
1075 """Called at the end of the verification phase of incremental OTA
1076 installation; additional checks can be placed here to abort the
1077 script before any changes are made."""
1078 return self._DoCall("IncrementalOTA_VerifyEnd")
1079
Doug Zongkere5ff5902012-01-17 10:55:37 -08001080 def IncrementalOTA_InstallBegin(self):
1081 """Called at the start of incremental OTA installation (after
1082 verification is complete)."""
1083 return self._DoCall("IncrementalOTA_InstallBegin")
1084
Doug Zongker05d3dea2009-06-22 11:32:31 -07001085 def IncrementalOTA_InstallEnd(self):
1086 """Called at the end of incremental OTA installation; typically
1087 this is used to install the image for the device's baseband
1088 processor."""
1089 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001090
Tao Bao9bc6bb22015-11-09 16:58:28 -08001091 def VerifyOTA_Assertions(self):
1092 return self._DoCall("VerifyOTA_Assertions")
1093
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001094class File(object):
1095 def __init__(self, name, data):
1096 self.name = name
1097 self.data = data
1098 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001099 self.sha1 = sha1(data).hexdigest()
1100
1101 @classmethod
1102 def FromLocalFile(cls, name, diskname):
1103 f = open(diskname, "rb")
1104 data = f.read()
1105 f.close()
1106 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001107
1108 def WriteToTemp(self):
1109 t = tempfile.NamedTemporaryFile()
1110 t.write(self.data)
1111 t.flush()
1112 return t
1113
Geremy Condra36bd3652014-02-06 19:45:10 -08001114 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001115 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001116
1117DIFF_PROGRAM_BY_EXT = {
1118 ".gz" : "imgdiff",
1119 ".zip" : ["imgdiff", "-z"],
1120 ".jar" : ["imgdiff", "-z"],
1121 ".apk" : ["imgdiff", "-z"],
1122 ".img" : "imgdiff",
1123 }
1124
1125class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001126 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001127 self.tf = tf
1128 self.sf = sf
1129 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001130 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001131
1132 def ComputePatch(self):
1133 """Compute the patch (as a string of data) needed to turn sf into
1134 tf. Returns the same tuple as GetPatch()."""
1135
1136 tf = self.tf
1137 sf = self.sf
1138
Doug Zongker24cd2802012-08-14 16:36:15 -07001139 if self.diff_program:
1140 diff_program = self.diff_program
1141 else:
1142 ext = os.path.splitext(tf.name)[1]
1143 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001144
1145 ttemp = tf.WriteToTemp()
1146 stemp = sf.WriteToTemp()
1147
1148 ext = os.path.splitext(tf.name)[1]
1149
1150 try:
1151 ptemp = tempfile.NamedTemporaryFile()
1152 if isinstance(diff_program, list):
1153 cmd = copy.copy(diff_program)
1154 else:
1155 cmd = [diff_program]
1156 cmd.append(stemp.name)
1157 cmd.append(ttemp.name)
1158 cmd.append(ptemp.name)
1159 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001160 err = []
1161 def run():
1162 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001163 if e:
1164 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001165 th = threading.Thread(target=run)
1166 th.start()
1167 th.join(timeout=300) # 5 mins
1168 if th.is_alive():
1169 print "WARNING: diff command timed out"
1170 p.terminate()
1171 th.join(5)
1172 if th.is_alive():
1173 p.kill()
1174 th.join()
1175
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001176 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001177 print "WARNING: failure running %s:\n%s\n" % (
1178 diff_program, "".join(err))
1179 self.patch = None
1180 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001181 diff = ptemp.read()
1182 finally:
1183 ptemp.close()
1184 stemp.close()
1185 ttemp.close()
1186
1187 self.patch = diff
1188 return self.tf, self.sf, self.patch
1189
1190
1191 def GetPatch(self):
1192 """Return a tuple (target_file, source_file, patch_data).
1193 patch_data may be None if ComputePatch hasn't been called, or if
1194 computing the patch failed."""
1195 return self.tf, self.sf, self.patch
1196
1197
1198def ComputeDifferences(diffs):
1199 """Call ComputePatch on all the Difference objects in 'diffs'."""
1200 print len(diffs), "diffs to compute"
1201
1202 # Do the largest files first, to try and reduce the long-pole effect.
1203 by_size = [(i.tf.size, i) for i in diffs]
1204 by_size.sort(reverse=True)
1205 by_size = [i[1] for i in by_size]
1206
1207 lock = threading.Lock()
1208 diff_iter = iter(by_size) # accessed under lock
1209
1210 def worker():
1211 try:
1212 lock.acquire()
1213 for d in diff_iter:
1214 lock.release()
1215 start = time.time()
1216 d.ComputePatch()
1217 dur = time.time() - start
1218 lock.acquire()
1219
1220 tf, sf, patch = d.GetPatch()
1221 if sf.name == tf.name:
1222 name = tf.name
1223 else:
1224 name = "%s (%s)" % (tf.name, sf.name)
1225 if patch is None:
1226 print "patching failed! %s" % (name,)
1227 else:
1228 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1229 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1230 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001231 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001232 print e
1233 raise
1234
1235 # start worker threads; wait for them all to finish.
1236 threads = [threading.Thread(target=worker)
1237 for i in range(OPTIONS.worker_threads)]
1238 for th in threads:
1239 th.start()
1240 while threads:
1241 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001242
1243
Dan Albert8b72aef2015-03-23 19:13:21 -07001244class BlockDifference(object):
1245 def __init__(self, partition, tgt, src=None, check_first_block=False,
1246 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001247 self.tgt = tgt
1248 self.src = src
1249 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001250 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001251
Tao Bao5ece99d2015-05-12 11:42:31 -07001252 # Due to http://b/20939131, check_first_block is disabled temporarily.
1253 assert not self.check_first_block
1254
Tao Baodd2a5892015-03-12 12:32:37 -07001255 if version is None:
1256 version = 1
1257 if OPTIONS.info_dict:
1258 version = max(
1259 int(i) for i in
1260 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1261 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001262
1263 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001264 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001265 tmpdir = tempfile.mkdtemp()
1266 OPTIONS.tempfiles.append(tmpdir)
1267 self.path = os.path.join(tmpdir, partition)
1268 b.Compute(self.path)
1269
Tao Baoaac4ad52015-10-16 15:26:34 -07001270 if src is None:
1271 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1272 else:
1273 _, self.device = GetTypeAndDevice("/" + partition,
1274 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001275
1276 def WriteScript(self, script, output_zip, progress=None):
1277 if not self.src:
1278 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001279 script.Print("Patching %s image unconditionally..." % (self.partition,))
1280 else:
1281 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001282
Dan Albert8b72aef2015-03-23 19:13:21 -07001283 if progress:
1284 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001285 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001286 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001287
Tao Bao9bc6bb22015-11-09 16:58:28 -08001288 def WriteStrictVerifyScript(self, script):
1289 """Verify all the blocks in the care_map, including clobbered blocks.
1290
1291 This differs from the WriteVerifyScript() function: a) it prints different
1292 error messages; b) it doesn't allow half-way updated images to pass the
1293 verification."""
1294
1295 partition = self.partition
1296 script.Print("Verifying %s..." % (partition,))
1297 ranges = self.tgt.care_map
1298 ranges_str = ranges.to_string_raw()
1299 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1300 'ui_print(" Verified.") || '
1301 'ui_print("\\"%s\\" has unexpected contents.");' % (
1302 self.device, ranges_str,
1303 self.tgt.TotalSha1(include_clobbered_blocks=True),
1304 self.device))
1305 script.AppendExtra("")
1306
Jesse Zhao75bcea02015-01-06 10:59:53 -08001307 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001308 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001309 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001310 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001311 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001312 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1313 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001314 if self.version >= 4:
1315 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1316 'block_image_verify("%s", '
1317 'package_extract_file("%s.transfer.list"), '
1318 '"%s.new.dat", "%s.patch.dat") || '
1319 '(block_image_recover("%s", "%s") && '
1320 'block_image_verify("%s", '
1321 'package_extract_file("%s.transfer.list"), '
1322 '"%s.new.dat", "%s.patch.dat"))) then') % (
1323 self.device, ranges_str, self.src.TotalSha1(),
1324 self.device, partition, partition, partition,
1325 self.device, ranges_str,
1326 self.device, partition, partition, partition))
1327 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001328 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1329 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001330 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001331 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001332 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001333 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001334 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001335 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001336 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001337 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001338 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001339
Tao Baodd2a5892015-03-12 12:32:37 -07001340 # When generating incrementals for the system and vendor partitions,
1341 # explicitly check the first block (which contains the superblock) of
1342 # the partition to see if it's what we expect. If this check fails,
1343 # give an explicit log message about the partition having been
1344 # remounted R/W (the most likely explanation) and the need to flash to
1345 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001346 if self.check_first_block:
1347 self._CheckFirstBlock(script)
1348
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.
1354 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1355 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001356
Tao Bao5fcaaef2015-06-01 13:40:49 -07001357 def _WritePostInstallVerifyScript(self, script):
1358 partition = self.partition
1359 script.Print('Verifying the updated %s image...' % (partition,))
1360 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1361 ranges = self.tgt.care_map
1362 ranges_str = ranges.to_string_raw()
1363 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1364 self.device, ranges_str,
1365 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001366
1367 # Bug: 20881595
1368 # Verify that extended blocks are really zeroed out.
1369 if self.tgt.extended:
1370 ranges_str = self.tgt.extended.to_string_raw()
1371 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1372 self.device, ranges_str,
1373 self._HashZeroBlocks(self.tgt.extended.size())))
1374 script.Print('Verified the updated %s image.' % (partition,))
1375 script.AppendExtra(
1376 'else\n'
1377 ' abort("%s partition has unexpected non-zero contents after OTA '
1378 'update");\n'
1379 'endif;' % (partition,))
1380 else:
1381 script.Print('Verified the updated %s image.' % (partition,))
1382
Tao Bao5fcaaef2015-06-01 13:40:49 -07001383 script.AppendExtra(
1384 'else\n'
1385 ' abort("%s partition has unexpected contents after OTA update");\n'
1386 'endif;' % (partition,))
1387
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001388 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001389 ZipWrite(output_zip,
1390 '{}.transfer.list'.format(self.path),
1391 '{}.transfer.list'.format(self.partition))
1392 ZipWrite(output_zip,
1393 '{}.new.dat'.format(self.path),
1394 '{}.new.dat'.format(self.partition))
1395 ZipWrite(output_zip,
1396 '{}.patch.dat'.format(self.path),
1397 '{}.patch.dat'.format(self.partition),
1398 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001399
Dan Albert8e0178d2015-01-27 15:53:15 -08001400 call = ('block_image_update("{device}", '
1401 'package_extract_file("{partition}.transfer.list"), '
1402 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1403 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001404 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001405
Dan Albert8b72aef2015-03-23 19:13:21 -07001406 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001407 data = source.ReadRangeSet(ranges)
1408 ctx = sha1()
1409
1410 for p in data:
1411 ctx.update(p)
1412
1413 return ctx.hexdigest()
1414
Tao Baoe9b61912015-07-09 17:37:49 -07001415 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1416 """Return the hash value for all zero blocks."""
1417 zero_block = '\x00' * 4096
1418 ctx = sha1()
1419 for _ in range(num_blocks):
1420 ctx.update(zero_block)
1421
1422 return ctx.hexdigest()
1423
Tao Bao5ece99d2015-05-12 11:42:31 -07001424 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1425 # remounting R/W. Will change the checking to a finer-grained way to
1426 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001427 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001428 r = rangelib.RangeSet((0, 1))
1429 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001430
1431 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1432 'abort("%s has been remounted R/W; '
1433 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001434 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001435 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001436
1437DataImage = blockimgdiff.DataImage
1438
1439
Doug Zongker96a57e72010-09-26 14:57:41 -07001440# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001441PARTITION_TYPES = {
1442 "yaffs2": "MTD",
1443 "mtd": "MTD",
1444 "ext4": "EMMC",
1445 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001446 "f2fs": "EMMC",
1447 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001448}
Doug Zongker96a57e72010-09-26 14:57:41 -07001449
1450def GetTypeAndDevice(mount_point, info):
1451 fstab = info["fstab"]
1452 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001453 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1454 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001455 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001456 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001457
1458
1459def ParseCertificate(data):
1460 """Parse a PEM-format certificate."""
1461 cert = []
1462 save = False
1463 for line in data.split("\n"):
1464 if "--END CERTIFICATE--" in line:
1465 break
1466 if save:
1467 cert.append(line)
1468 if "--BEGIN CERTIFICATE--" in line:
1469 save = True
1470 cert = "".join(cert).decode('base64')
1471 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001472
Doug Zongker412c02f2014-02-13 10:58:24 -08001473def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1474 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001475 """Generate a binary patch that creates the recovery image starting
1476 with the boot image. (Most of the space in these images is just the
1477 kernel, which is identical for the two, so the resulting patch
1478 should be efficient.) Add it to the output zip, along with a shell
1479 script that is run from init.rc on first boot to actually do the
1480 patching and install the new recovery image.
1481
1482 recovery_img and boot_img should be File objects for the
1483 corresponding images. info should be the dictionary returned by
1484 common.LoadInfoDict() on the input target_files.
1485 """
1486
Doug Zongker412c02f2014-02-13 10:58:24 -08001487 if info_dict is None:
1488 info_dict = OPTIONS.info_dict
1489
Tao Baof2cffbd2015-07-22 12:33:18 -07001490 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001491 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001492
Tao Baof2cffbd2015-07-22 12:33:18 -07001493 if full_recovery_image:
1494 output_sink("etc/recovery.img", recovery_img.data)
1495
1496 else:
1497 diff_program = ["imgdiff"]
1498 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1499 if os.path.exists(path):
1500 diff_program.append("-b")
1501 diff_program.append(path)
1502 bonus_args = "-b /system/etc/recovery-resource.dat"
1503 else:
1504 bonus_args = ""
1505
1506 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1507 _, _, patch = d.ComputePatch()
1508 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001509
Dan Albertebb19aa2015-03-27 19:11:53 -07001510 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001511 # The following GetTypeAndDevice()s need to use the path in the target
1512 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001513 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1514 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1515 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001516 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001517
Tao Baof2cffbd2015-07-22 12:33:18 -07001518 if full_recovery_image:
1519 sh = """#!/system/bin/sh
1520if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1521 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"
1522else
1523 log -t recovery "Recovery image already installed"
1524fi
1525""" % {'type': recovery_type,
1526 'device': recovery_device,
1527 'sha1': recovery_img.sha1,
1528 'size': recovery_img.size}
1529 else:
1530 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001531if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1532 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"
1533else
1534 log -t recovery "Recovery image already installed"
1535fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001536""" % {'boot_size': boot_img.size,
1537 'boot_sha1': boot_img.sha1,
1538 'recovery_size': recovery_img.size,
1539 'recovery_sha1': recovery_img.sha1,
1540 'boot_type': boot_type,
1541 'boot_device': boot_device,
1542 'recovery_type': recovery_type,
1543 'recovery_device': recovery_device,
1544 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001545
1546 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001547 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001548 # target-files expects it to be, and put it there.
1549 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001550 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001551 if system_root_image:
1552 init_rc_dir = os.path.join(input_dir, "ROOT")
1553 else:
1554 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001555 init_rc_files = os.listdir(init_rc_dir)
1556 for init_rc_file in init_rc_files:
1557 if (not init_rc_file.startswith('init.') or
1558 not init_rc_file.endswith('.rc')):
1559 continue
1560
1561 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001562 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001563 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001564 if m:
1565 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001566 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001567 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001568
1569 if found:
1570 break
1571
1572 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001573
1574 output_sink(sh_location, sh)