blob: 764a8908b5ce66785a0bf627ac8f0cae11131d5e [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
62 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070063 # Stash size cannot exceed cache_size * threshold.
64 self.cache_size = None
65 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070066
67
68OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070069
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080070
71# Values for "certificate" in apkcerts that mean special things.
72SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
73
74
Dan Albert8b72aef2015-03-23 19:13:21 -070075class ExternalError(RuntimeError):
76 pass
Doug Zongkereef39442009-04-02 12:14:19 -070077
78
79def Run(args, **kwargs):
80 """Create and return a subprocess.Popen object, printing the command
81 line on the terminal if -v was specified."""
82 if OPTIONS.verbose:
83 print " running: ", " ".join(args)
84 return subprocess.Popen(args, **kwargs)
85
86
Ying Wang7e6d4e42010-12-13 16:25:36 -080087def CloseInheritedPipes():
88 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
89 before doing other work."""
90 if platform.system() != "Darwin":
91 return
92 for d in range(3, 1025):
93 try:
94 stat = os.fstat(d)
95 if stat is not None:
96 pipebit = stat[0] & 0x1000
97 if pipebit != 0:
98 os.close(d)
99 except OSError:
100 pass
101
102
Tao Bao2c15d9e2015-07-09 11:51:16 -0700103def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700104 """Read and parse the META/misc_info.txt key/value pairs from the
105 input target files and return a dict."""
106
Doug Zongkerc9253822014-02-04 12:17:58 -0800107 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700108 if isinstance(input_file, zipfile.ZipFile):
109 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800110 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700111 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800112 try:
113 with open(path) as f:
114 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700115 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800116 if e.errno == errno.ENOENT:
117 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700118 d = {}
119 try:
Michael Runge6e836112014-04-15 17:40:21 -0700120 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700121 except KeyError:
122 # ok if misc_info.txt doesn't exist
123 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700124
Doug Zongker37974732010-09-16 17:44:38 -0700125 # backwards compatibility: These values used to be in their own
126 # files. Look for them, in case we're processing an old
127 # target_files zip.
128
129 if "mkyaffs2_extra_flags" not in d:
130 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700131 d["mkyaffs2_extra_flags"] = read_helper(
132 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700133 except KeyError:
134 # ok if flags don't exist
135 pass
136
137 if "recovery_api_version" not in d:
138 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700139 d["recovery_api_version"] = read_helper(
140 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700141 except KeyError:
142 raise ValueError("can't find recovery API version in input target-files")
143
144 if "tool_extensions" not in d:
145 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800146 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700147 except KeyError:
148 # ok if extensions don't exist
149 pass
150
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800151 if "fstab_version" not in d:
152 d["fstab_version"] = "1"
153
Tao Bao84e75682015-07-19 02:38:53 -0700154 # A few properties are stored as links to the files in the out/ directory.
155 # It works fine with the build system. However, they are no longer available
156 # when (re)generating from target_files zip. If input_dir is not None, we
157 # are doing repacking. Redirect those properties to the actual files in the
158 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700159 if input_dir is not None:
Tao Bao84e75682015-07-19 02:38:53 -0700160 # We carry a copy of file_contexts under META/. If not available, search
161 # BOOT/RAMDISK/. Note that sometimes we may need a different file_contexts
162 # to build images than the one running on device, such as when enabling
163 # system_root_image. In that case, we must have the one for image
164 # generation copied to META/.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700165 fc_config = os.path.join(input_dir, "META", "file_contexts")
Tao Bao84e75682015-07-19 02:38:53 -0700166 if d.get("system_root_image") == "true":
167 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700168 if not os.path.exists(fc_config):
169 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", "file_contexts")
170 if not os.path.exists(fc_config):
171 fc_config = None
172
173 if fc_config:
174 d["selinux_fc"] = fc_config
175
Tao Bao84e75682015-07-19 02:38:53 -0700176 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
177 if d.get("system_root_image") == "true":
178 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
179 d["ramdisk_fs_config"] = os.path.join(
180 input_dir, "META", "root_filesystem_config.txt")
181
Doug Zongker37974732010-09-16 17:44:38 -0700182 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800183 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700184 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700185 if not line:
186 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700187 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700188 if not value:
189 continue
Doug Zongker37974732010-09-16 17:44:38 -0700190 if name == "blocksize":
191 d[name] = value
192 else:
193 d[name + "_size"] = value
194 except KeyError:
195 pass
196
197 def makeint(key):
198 if key in d:
199 d[key] = int(d[key], 0)
200
201 makeint("recovery_api_version")
202 makeint("blocksize")
203 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700204 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700205 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700206 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700207 makeint("recovery_size")
208 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800209 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700210
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700211 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
212 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800213 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700214 return d
215
Doug Zongkerc9253822014-02-04 12:17:58 -0800216def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700217 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800218 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700219 except KeyError:
220 print "Warning: could not find SYSTEM/build.prop in %s" % zip
221 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700222 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700223
Michael Runge6e836112014-04-15 17:40:21 -0700224def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700225 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700226 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700227 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700228 if not line or line.startswith("#"):
229 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700230 if "=" in line:
231 name, value = line.split("=", 1)
232 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700233 return d
234
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700235def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700236 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700237 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700238 self.mount_point = mount_point
239 self.fs_type = fs_type
240 self.device = device
241 self.length = length
242 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700243 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700244
245 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800246 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700247 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800248 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700249 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700250
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800251 if fstab_version == 1:
252 d = {}
253 for line in data.split("\n"):
254 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700255 if not line or line.startswith("#"):
256 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800257 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700258 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800259 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800260 options = None
261 if len(pieces) >= 4:
262 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700263 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800264 if len(pieces) >= 5:
265 options = pieces[4]
266 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700267 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800268 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800269 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700270 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700271
Dan Albert8b72aef2015-03-23 19:13:21 -0700272 mount_point = pieces[0]
273 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800274 if options:
275 options = options.split(",")
276 for i in options:
277 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700278 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800279 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700280 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800281
Dan Albert8b72aef2015-03-23 19:13:21 -0700282 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
283 device=pieces[2], length=length,
284 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800285
286 elif fstab_version == 2:
287 d = {}
288 for line in data.split("\n"):
289 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700290 if not line or line.startswith("#"):
291 continue
Tao Bao548eb762015-06-10 12:32:41 -0700292 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800293 pieces = line.split()
294 if len(pieces) != 5:
295 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
296
297 # Ignore entries that are managed by vold
298 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700299 if "voldmanaged=" in options:
300 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800301
302 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700303 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800304 options = options.split(",")
305 for i in options:
306 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700307 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800308 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800309 # Ignore all unknown options in the unified fstab
310 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800311
Tao Bao548eb762015-06-10 12:32:41 -0700312 mount_flags = pieces[3]
313 # Honor the SELinux context if present.
314 context = None
315 for i in mount_flags.split(","):
316 if i.startswith("context="):
317 context = i
318
Dan Albert8b72aef2015-03-23 19:13:21 -0700319 mount_point = pieces[1]
320 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700321 device=pieces[0], length=length,
322 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800323
324 else:
325 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
326
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700327 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700328 # system. Other areas assume system is always at "/system" so point /system
329 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700330 if system_root_image:
331 assert not d.has_key("/system") and d.has_key("/")
332 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700333 return d
334
335
Doug Zongker37974732010-09-16 17:44:38 -0700336def DumpInfoDict(d):
337 for k, v in sorted(d.items()):
338 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700339
Dan Albert8b72aef2015-03-23 19:13:21 -0700340
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700341def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
342 has_ramdisk=False):
343 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700344
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700345 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
346 'sourcedir'), and turn them into a boot image. Return the image data, or
347 None if sourcedir does not appear to contains files for building the
348 requested image."""
349
350 def make_ramdisk():
351 ramdisk_img = tempfile.NamedTemporaryFile()
352
353 if os.access(fs_config_file, os.F_OK):
354 cmd = ["mkbootfs", "-f", fs_config_file,
355 os.path.join(sourcedir, "RAMDISK")]
356 else:
357 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
358 p1 = Run(cmd, stdout=subprocess.PIPE)
359 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
360
361 p2.wait()
362 p1.wait()
363 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
364 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
365
366 return ramdisk_img
367
368 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
369 return None
370
371 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700372 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700373
Doug Zongkerd5131602012-08-02 14:46:42 -0700374 if info_dict is None:
375 info_dict = OPTIONS.info_dict
376
Doug Zongkereef39442009-04-02 12:14:19 -0700377 img = tempfile.NamedTemporaryFile()
378
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700379 if has_ramdisk:
380 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700381
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800382 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
383 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
384
385 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700386
Benoit Fradina45a8682014-07-14 21:00:43 +0200387 fn = os.path.join(sourcedir, "second")
388 if os.access(fn, os.F_OK):
389 cmd.append("--second")
390 cmd.append(fn)
391
Doug Zongker171f1cd2009-06-15 22:36:37 -0700392 fn = os.path.join(sourcedir, "cmdline")
393 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700394 cmd.append("--cmdline")
395 cmd.append(open(fn).read().rstrip("\n"))
396
397 fn = os.path.join(sourcedir, "base")
398 if os.access(fn, os.F_OK):
399 cmd.append("--base")
400 cmd.append(open(fn).read().rstrip("\n"))
401
Ying Wang4de6b5b2010-08-25 14:29:34 -0700402 fn = os.path.join(sourcedir, "pagesize")
403 if os.access(fn, os.F_OK):
404 cmd.append("--pagesize")
405 cmd.append(open(fn).read().rstrip("\n"))
406
Doug Zongkerd5131602012-08-02 14:46:42 -0700407 args = info_dict.get("mkbootimg_args", None)
408 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700409 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700410
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700411 if has_ramdisk:
412 cmd.extend(["--ramdisk", ramdisk_img.name])
413
Tao Baod95e9fd2015-03-29 23:07:41 -0700414 img_unsigned = None
415 if info_dict.get("vboot", None):
416 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700417 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700418 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700419 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700420
421 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700422 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700423 assert p.returncode == 0, "mkbootimg of %s image failed" % (
424 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700425
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100426 if (info_dict.get("boot_signer", None) == "true" and
427 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700428 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700429 cmd = [OPTIONS.boot_signer_path]
430 cmd.extend(OPTIONS.boot_signer_args)
431 cmd.extend([path, img.name,
432 info_dict["verity_key"] + ".pk8",
433 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700434 p = Run(cmd, stdout=subprocess.PIPE)
435 p.communicate()
436 assert p.returncode == 0, "boot_signer of %s image failed" % path
437
Tao Baod95e9fd2015-03-29 23:07:41 -0700438 # Sign the image if vboot is non-empty.
439 elif info_dict.get("vboot", None):
440 path = "/" + os.path.basename(sourcedir).lower()
441 img_keyblock = tempfile.NamedTemporaryFile()
442 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
443 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700444 info_dict["vboot_key"] + ".vbprivk",
445 info_dict["vboot_subkey"] + ".vbprivk",
446 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700447 img.name]
448 p = Run(cmd, stdout=subprocess.PIPE)
449 p.communicate()
450 assert p.returncode == 0, "vboot_signer of %s image failed" % path
451
Tao Baof3282b42015-04-01 11:21:55 -0700452 # Clean up the temp files.
453 img_unsigned.close()
454 img_keyblock.close()
455
Doug Zongkereef39442009-04-02 12:14:19 -0700456 img.seek(os.SEEK_SET, 0)
457 data = img.read()
458
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700459 if has_ramdisk:
460 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700461 img.close()
462
463 return data
464
465
Doug Zongkerd5131602012-08-02 14:46:42 -0700466def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
467 info_dict=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700468 """Return a File object with the desired bootable image.
469
470 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
471 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
472 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700473
Doug Zongker55d93282011-01-25 17:03:34 -0800474 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
475 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700476 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800477 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700478
479 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
480 if os.path.exists(prebuilt_path):
481 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
482 return File.FromLocalFile(name, prebuilt_path)
483
484 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700485
486 if info_dict is None:
487 info_dict = OPTIONS.info_dict
488
489 # With system_root_image == "true", we don't pack ramdisk into the boot image.
490 has_ramdisk = (info_dict.get("system_root_image", None) != "true" or
491 prebuilt_name != "boot.img")
492
Doug Zongker6f1d0312014-08-22 08:07:12 -0700493 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700494 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
495 os.path.join(unpack_dir, fs_config),
496 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700497 if data:
498 return File(name, data)
499 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800500
Doug Zongkereef39442009-04-02 12:14:19 -0700501
Doug Zongker75f17362009-12-08 13:46:44 -0800502def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800503 """Unzip the given archive into a temporary directory and return the name.
504
505 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
506 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
507
508 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
509 main file), open for reading.
510 """
Doug Zongkereef39442009-04-02 12:14:19 -0700511
512 tmp = tempfile.mkdtemp(prefix="targetfiles-")
513 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800514
515 def unzip_to_dir(filename, dirname):
516 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
517 if pattern is not None:
518 cmd.append(pattern)
519 p = Run(cmd, stdout=subprocess.PIPE)
520 p.communicate()
521 if p.returncode != 0:
522 raise ExternalError("failed to unzip input target-files \"%s\"" %
523 (filename,))
524
525 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
526 if m:
527 unzip_to_dir(m.group(1), tmp)
528 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
529 filename = m.group(1)
530 else:
531 unzip_to_dir(filename, tmp)
532
533 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700534
535
536def GetKeyPasswords(keylist):
537 """Given a list of keys, prompt the user to enter passwords for
538 those which require them. Return a {key: password} dict. password
539 will be None if the key has no password."""
540
Doug Zongker8ce7c252009-05-22 13:34:54 -0700541 no_passwords = []
542 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700543 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700544 devnull = open("/dev/null", "w+b")
545 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800546 # We don't need a password for things that aren't really keys.
547 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700548 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700549 continue
550
T.R. Fullhart37e10522013-03-18 10:31:26 -0700551 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700552 "-inform", "DER", "-nocrypt"],
553 stdin=devnull.fileno(),
554 stdout=devnull.fileno(),
555 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700556 p.communicate()
557 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700558 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700559 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700560 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700561 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
562 "-inform", "DER", "-passin", "pass:"],
563 stdin=devnull.fileno(),
564 stdout=devnull.fileno(),
565 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700566 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700567 if p.returncode == 0:
568 # Encrypted key with empty string as password.
569 key_passwords[k] = ''
570 elif stderr.startswith('Error decrypting key'):
571 # Definitely encrypted key.
572 # It would have said "Error reading key" if it didn't parse correctly.
573 need_passwords.append(k)
574 else:
575 # Potentially, a type of key that openssl doesn't understand.
576 # We'll let the routines in signapk.jar handle it.
577 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700578 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700579
T.R. Fullhart37e10522013-03-18 10:31:26 -0700580 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700581 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700582 return key_passwords
583
584
Doug Zongker951495f2009-08-14 12:44:19 -0700585def SignFile(input_name, output_name, key, password, align=None,
586 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700587 """Sign the input_name zip/jar/apk, producing output_name. Use the
588 given key and password (the latter may be None if the key does not
589 have a password.
590
591 If align is an integer > 1, zipalign is run to align stored files in
592 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700593
594 If whole_file is true, use the "-w" option to SignApk to embed a
595 signature that covers the whole file in the archive comment of the
596 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700597 """
Doug Zongker951495f2009-08-14 12:44:19 -0700598
Doug Zongkereef39442009-04-02 12:14:19 -0700599 if align == 0 or align == 1:
600 align = None
601
602 if align:
603 temp = tempfile.NamedTemporaryFile()
604 sign_name = temp.name
605 else:
606 sign_name = output_name
607
Baligh Uddin339ee492014-09-05 11:18:07 -0700608 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700609 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
610 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700611 if whole_file:
612 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700613 cmd.extend([key + OPTIONS.public_key_suffix,
614 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700615 input_name, sign_name])
616
617 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700618 if password is not None:
619 password += "\n"
620 p.communicate(password)
621 if p.returncode != 0:
622 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
623
624 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700625 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700626 p.communicate()
627 if p.returncode != 0:
628 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
629 temp.close()
630
631
Doug Zongker37974732010-09-16 17:44:38 -0700632def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700633 """Check the data string passed against the max size limit, if
634 any, for the given target. Raise exception if the data is too big.
635 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700636
Dan Albert8b72aef2015-03-23 19:13:21 -0700637 if target.endswith(".img"):
638 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700639 mount_point = "/" + target
640
Ying Wangf8824af2014-06-03 14:07:27 -0700641 fs_type = None
642 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700643 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700644 if mount_point == "/userdata":
645 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700646 p = info_dict["fstab"][mount_point]
647 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800648 device = p.device
649 if "/" in device:
650 device = device[device.rfind("/")+1:]
651 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700652 if not fs_type or not limit:
653 return
Doug Zongkereef39442009-04-02 12:14:19 -0700654
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700655 if fs_type == "yaffs2":
656 # image size should be increased by 1/64th to account for the
657 # spare area (64 bytes per 2k page)
658 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800659 size = len(data)
660 pct = float(size) * 100.0 / limit
661 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
662 if pct >= 99.0:
663 raise ExternalError(msg)
664 elif pct >= 95.0:
665 print
666 print " WARNING: ", msg
667 print
668 elif OPTIONS.verbose:
669 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700670
671
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800672def ReadApkCerts(tf_zip):
673 """Given a target_files ZipFile, parse the META/apkcerts.txt file
674 and return a {package: cert} dict."""
675 certmap = {}
676 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
677 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700678 if not line:
679 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800680 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
681 r'private_key="(.*)"$', line)
682 if m:
683 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700684 public_key_suffix_len = len(OPTIONS.public_key_suffix)
685 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800686 if cert in SPECIAL_CERT_STRINGS and not privkey:
687 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700688 elif (cert.endswith(OPTIONS.public_key_suffix) and
689 privkey.endswith(OPTIONS.private_key_suffix) and
690 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
691 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800692 else:
693 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
694 return certmap
695
696
Doug Zongkereef39442009-04-02 12:14:19 -0700697COMMON_DOCSTRING = """
698 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700699 Prepend <dir>/bin to the list of places to search for binaries
700 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700701
Doug Zongker05d3dea2009-06-22 11:32:31 -0700702 -s (--device_specific) <file>
703 Path to the python module containing device-specific
704 releasetools code.
705
Doug Zongker8bec09e2009-11-30 15:37:14 -0800706 -x (--extra) <key=value>
707 Add a key/value pair to the 'extras' dict, which device-specific
708 extension code may look at.
709
Doug Zongkereef39442009-04-02 12:14:19 -0700710 -v (--verbose)
711 Show command lines being executed.
712
713 -h (--help)
714 Display this usage message and exit.
715"""
716
717def Usage(docstring):
718 print docstring.rstrip("\n")
719 print COMMON_DOCSTRING
720
721
722def ParseOptions(argv,
723 docstring,
724 extra_opts="", extra_long_opts=(),
725 extra_option_handler=None):
726 """Parse the options in argv and return any arguments that aren't
727 flags. docstring is the calling module's docstring, to be displayed
728 for errors and -h. extra_opts and extra_long_opts are for flags
729 defined by the caller, which are processed by passing them to
730 extra_option_handler."""
731
732 try:
733 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800734 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700735 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700736 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700737 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
738 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800739 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700740 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700741 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700742 Usage(docstring)
743 print "**", str(err), "**"
744 sys.exit(2)
745
Doug Zongkereef39442009-04-02 12:14:19 -0700746 for o, a in opts:
747 if o in ("-h", "--help"):
748 Usage(docstring)
749 sys.exit()
750 elif o in ("-v", "--verbose"):
751 OPTIONS.verbose = True
752 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700753 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700754 elif o in ("--signapk_path",):
755 OPTIONS.signapk_path = a
756 elif o in ("--extra_signapk_args",):
757 OPTIONS.extra_signapk_args = shlex.split(a)
758 elif o in ("--java_path",):
759 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700760 elif o in ("--java_args",):
761 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700762 elif o in ("--public_key_suffix",):
763 OPTIONS.public_key_suffix = a
764 elif o in ("--private_key_suffix",):
765 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800766 elif o in ("--boot_signer_path",):
767 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700768 elif o in ("--boot_signer_args",):
769 OPTIONS.boot_signer_args = shlex.split(a)
770 elif o in ("--verity_signer_path",):
771 OPTIONS.verity_signer_path = a
772 elif o in ("--verity_signer_args",):
773 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700774 elif o in ("-s", "--device_specific"):
775 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800776 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800777 key, value = a.split("=", 1)
778 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700779 else:
780 if extra_option_handler is None or not extra_option_handler(o, a):
781 assert False, "unknown option \"%s\"" % (o,)
782
Doug Zongker85448772014-09-09 14:59:20 -0700783 if OPTIONS.search_path:
784 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
785 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700786
787 return args
788
789
Doug Zongkerfc44a512014-08-26 13:10:25 -0700790def MakeTempFile(prefix=None, suffix=None):
791 """Make a temp file and add it to the list of things to be deleted
792 when Cleanup() is called. Return the filename."""
793 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
794 os.close(fd)
795 OPTIONS.tempfiles.append(fn)
796 return fn
797
798
Doug Zongkereef39442009-04-02 12:14:19 -0700799def Cleanup():
800 for i in OPTIONS.tempfiles:
801 if os.path.isdir(i):
802 shutil.rmtree(i)
803 else:
804 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700805
806
807class PasswordManager(object):
808 def __init__(self):
809 self.editor = os.getenv("EDITOR", None)
810 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
811
812 def GetPasswords(self, items):
813 """Get passwords corresponding to each string in 'items',
814 returning a dict. (The dict may have keys in addition to the
815 values in 'items'.)
816
817 Uses the passwords in $ANDROID_PW_FILE if available, letting the
818 user edit that file to add more needed passwords. If no editor is
819 available, or $ANDROID_PW_FILE isn't define, prompts the user
820 interactively in the ordinary way.
821 """
822
823 current = self.ReadFile()
824
825 first = True
826 while True:
827 missing = []
828 for i in items:
829 if i not in current or not current[i]:
830 missing.append(i)
831 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700832 if not missing:
833 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700834
835 for i in missing:
836 current[i] = ""
837
838 if not first:
839 print "key file %s still missing some passwords." % (self.pwfile,)
840 answer = raw_input("try to edit again? [y]> ").strip()
841 if answer and answer[0] not in 'yY':
842 raise RuntimeError("key passwords unavailable")
843 first = False
844
845 current = self.UpdateAndReadFile(current)
846
Dan Albert8b72aef2015-03-23 19:13:21 -0700847 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700848 """Prompt the user to enter a value (password) for each key in
849 'current' whose value is fales. Returns a new dict with all the
850 values.
851 """
852 result = {}
853 for k, v in sorted(current.iteritems()):
854 if v:
855 result[k] = v
856 else:
857 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700858 result[k] = getpass.getpass(
859 "Enter password for %s key> " % k).strip()
860 if result[k]:
861 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700862 return result
863
864 def UpdateAndReadFile(self, current):
865 if not self.editor or not self.pwfile:
866 return self.PromptResult(current)
867
868 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700869 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700870 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
871 f.write("# (Additional spaces are harmless.)\n\n")
872
873 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700874 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
875 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700876 f.write("[[[ %s ]]] %s\n" % (v, k))
877 if not v and first_line is None:
878 # position cursor on first line with no password.
879 first_line = i + 4
880 f.close()
881
882 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
883 _, _ = p.communicate()
884
885 return self.ReadFile()
886
887 def ReadFile(self):
888 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700889 if self.pwfile is None:
890 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700891 try:
892 f = open(self.pwfile, "r")
893 for line in f:
894 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700895 if not line or line[0] == '#':
896 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700897 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
898 if not m:
899 print "failed to parse password file: ", line
900 else:
901 result[m.group(2)] = m.group(1)
902 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700903 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700904 if e.errno != errno.ENOENT:
905 print "error reading password file: ", str(e)
906 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700907
908
Dan Albert8e0178d2015-01-27 15:53:15 -0800909def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
910 compress_type=None):
911 import datetime
912
913 # http://b/18015246
914 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
915 # for files larger than 2GiB. We can work around this by adjusting their
916 # limit. Note that `zipfile.writestr()` will not work for strings larger than
917 # 2GiB. The Python interpreter sometimes rejects strings that large (though
918 # it isn't clear to me exactly what circumstances cause this).
919 # `zipfile.write()` must be used directly to work around this.
920 #
921 # This mess can be avoided if we port to python3.
922 saved_zip64_limit = zipfile.ZIP64_LIMIT
923 zipfile.ZIP64_LIMIT = (1 << 32) - 1
924
925 if compress_type is None:
926 compress_type = zip_file.compression
927 if arcname is None:
928 arcname = filename
929
930 saved_stat = os.stat(filename)
931
932 try:
933 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
934 # file to be zipped and reset it when we're done.
935 os.chmod(filename, perms)
936
937 # Use a fixed timestamp so the output is repeatable.
938 epoch = datetime.datetime.fromtimestamp(0)
939 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
940 os.utime(filename, (timestamp, timestamp))
941
942 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
943 finally:
944 os.chmod(filename, saved_stat.st_mode)
945 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
946 zipfile.ZIP64_LIMIT = saved_zip64_limit
947
948
Tao Bao58c1b962015-05-20 09:32:18 -0700949def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700950 compress_type=None):
951 """Wrap zipfile.writestr() function to work around the zip64 limit.
952
953 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
954 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
955 when calling crc32(bytes).
956
957 But it still works fine to write a shorter string into a large zip file.
958 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
959 when we know the string won't be too long.
960 """
961
962 saved_zip64_limit = zipfile.ZIP64_LIMIT
963 zipfile.ZIP64_LIMIT = (1 << 32) - 1
964
965 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
966 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700967 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700968 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700969 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800970 else:
Tao Baof3282b42015-04-01 11:21:55 -0700971 zinfo = zinfo_or_arcname
972
973 # If compress_type is given, it overrides the value in zinfo.
974 if compress_type is not None:
975 zinfo.compress_type = compress_type
976
Tao Bao58c1b962015-05-20 09:32:18 -0700977 # If perms is given, it has a priority.
978 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700979 # If perms doesn't set the file type, mark it as a regular file.
980 if perms & 0o770000 == 0:
981 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -0700982 zinfo.external_attr = perms << 16
983
Tao Baof3282b42015-04-01 11:21:55 -0700984 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700985 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
986
Dan Albert8b72aef2015-03-23 19:13:21 -0700987 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700988 zipfile.ZIP64_LIMIT = saved_zip64_limit
989
990
991def ZipClose(zip_file):
992 # http://b/18015246
993 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
994 # central directory.
995 saved_zip64_limit = zipfile.ZIP64_LIMIT
996 zipfile.ZIP64_LIMIT = (1 << 32) - 1
997
998 zip_file.close()
999
1000 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001001
1002
1003class DeviceSpecificParams(object):
1004 module = None
1005 def __init__(self, **kwargs):
1006 """Keyword arguments to the constructor become attributes of this
1007 object, which is passed to all functions in the device-specific
1008 module."""
1009 for k, v in kwargs.iteritems():
1010 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001011 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001012
1013 if self.module is None:
1014 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001015 if not path:
1016 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001017 try:
1018 if os.path.isdir(path):
1019 info = imp.find_module("releasetools", [path])
1020 else:
1021 d, f = os.path.split(path)
1022 b, x = os.path.splitext(f)
1023 if x == ".py":
1024 f = b
1025 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001026 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001027 self.module = imp.load_module("device_specific", *info)
1028 except ImportError:
1029 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001030
1031 def _DoCall(self, function_name, *args, **kwargs):
1032 """Call the named function in the device-specific module, passing
1033 the given args and kwargs. The first argument to the call will be
1034 the DeviceSpecific object itself. If there is no module, or the
1035 module does not define the function, return the value of the
1036 'default' kwarg (which itself defaults to None)."""
1037 if self.module is None or not hasattr(self.module, function_name):
1038 return kwargs.get("default", None)
1039 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1040
1041 def FullOTA_Assertions(self):
1042 """Called after emitting the block of assertions at the top of a
1043 full OTA package. Implementations can add whatever additional
1044 assertions they like."""
1045 return self._DoCall("FullOTA_Assertions")
1046
Doug Zongkere5ff5902012-01-17 10:55:37 -08001047 def FullOTA_InstallBegin(self):
1048 """Called at the start of full OTA installation."""
1049 return self._DoCall("FullOTA_InstallBegin")
1050
Doug Zongker05d3dea2009-06-22 11:32:31 -07001051 def FullOTA_InstallEnd(self):
1052 """Called at the end of full OTA installation; typically this is
1053 used to install the image for the device's baseband processor."""
1054 return self._DoCall("FullOTA_InstallEnd")
1055
1056 def IncrementalOTA_Assertions(self):
1057 """Called after emitting the block of assertions at the top of an
1058 incremental OTA package. Implementations can add whatever
1059 additional assertions they like."""
1060 return self._DoCall("IncrementalOTA_Assertions")
1061
Doug Zongkere5ff5902012-01-17 10:55:37 -08001062 def IncrementalOTA_VerifyBegin(self):
1063 """Called at the start of the verification phase of incremental
1064 OTA installation; additional checks can be placed here to abort
1065 the script before any changes are made."""
1066 return self._DoCall("IncrementalOTA_VerifyBegin")
1067
Doug Zongker05d3dea2009-06-22 11:32:31 -07001068 def IncrementalOTA_VerifyEnd(self):
1069 """Called at the end of the verification phase of incremental OTA
1070 installation; additional checks can be placed here to abort the
1071 script before any changes are made."""
1072 return self._DoCall("IncrementalOTA_VerifyEnd")
1073
Doug Zongkere5ff5902012-01-17 10:55:37 -08001074 def IncrementalOTA_InstallBegin(self):
1075 """Called at the start of incremental OTA installation (after
1076 verification is complete)."""
1077 return self._DoCall("IncrementalOTA_InstallBegin")
1078
Doug Zongker05d3dea2009-06-22 11:32:31 -07001079 def IncrementalOTA_InstallEnd(self):
1080 """Called at the end of incremental OTA installation; typically
1081 this is used to install the image for the device's baseband
1082 processor."""
1083 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001084
1085class 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 Bao5ece99d2015-05-12 11:42:31 -07001243 # Due to http://b/20939131, check_first_block is disabled temporarily.
1244 assert not self.check_first_block
1245
Tao Baodd2a5892015-03-12 12:32:37 -07001246 if version is None:
1247 version = 1
1248 if OPTIONS.info_dict:
1249 version = max(
1250 int(i) for i in
1251 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1252 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001253
1254 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001255 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001256 tmpdir = tempfile.mkdtemp()
1257 OPTIONS.tempfiles.append(tmpdir)
1258 self.path = os.path.join(tmpdir, partition)
1259 b.Compute(self.path)
1260
1261 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1262
1263 def WriteScript(self, script, output_zip, progress=None):
1264 if not self.src:
1265 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001266 script.Print("Patching %s image unconditionally..." % (self.partition,))
1267 else:
1268 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001269
Dan Albert8b72aef2015-03-23 19:13:21 -07001270 if progress:
1271 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001272 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001273 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001274
1275 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001276 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001277 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001278 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001279 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001280 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1281 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001282 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001283 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1284 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001285 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001286 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001287 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001288 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001289 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001290 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001291 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001292 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001293 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001294
Tao Baodd2a5892015-03-12 12:32:37 -07001295 # When generating incrementals for the system and vendor partitions,
1296 # explicitly check the first block (which contains the superblock) of
1297 # the partition to see if it's what we expect. If this check fails,
1298 # give an explicit log message about the partition having been
1299 # remounted R/W (the most likely explanation) and the need to flash to
1300 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001301 if self.check_first_block:
1302 self._CheckFirstBlock(script)
1303
Tao Baodd2a5892015-03-12 12:32:37 -07001304 # Abort the OTA update. Note that the incremental OTA cannot be applied
1305 # even if it may match the checksum of the target partition.
1306 # a) If version < 3, operations like move and erase will make changes
1307 # unconditionally and damage the partition.
1308 # b) If version >= 3, it won't even reach here.
1309 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1310 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001311
Tao Bao5fcaaef2015-06-01 13:40:49 -07001312 def _WritePostInstallVerifyScript(self, script):
1313 partition = self.partition
1314 script.Print('Verifying the updated %s image...' % (partition,))
1315 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1316 ranges = self.tgt.care_map
1317 ranges_str = ranges.to_string_raw()
1318 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1319 self.device, ranges_str,
1320 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001321
1322 # Bug: 20881595
1323 # Verify that extended blocks are really zeroed out.
1324 if self.tgt.extended:
1325 ranges_str = self.tgt.extended.to_string_raw()
1326 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1327 self.device, ranges_str,
1328 self._HashZeroBlocks(self.tgt.extended.size())))
1329 script.Print('Verified the updated %s image.' % (partition,))
1330 script.AppendExtra(
1331 'else\n'
1332 ' abort("%s partition has unexpected non-zero contents after OTA '
1333 'update");\n'
1334 'endif;' % (partition,))
1335 else:
1336 script.Print('Verified the updated %s image.' % (partition,))
1337
Tao Bao5fcaaef2015-06-01 13:40:49 -07001338 script.AppendExtra(
1339 'else\n'
1340 ' abort("%s partition has unexpected contents after OTA update");\n'
1341 'endif;' % (partition,))
1342
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001343 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001344 ZipWrite(output_zip,
1345 '{}.transfer.list'.format(self.path),
1346 '{}.transfer.list'.format(self.partition))
1347 ZipWrite(output_zip,
1348 '{}.new.dat'.format(self.path),
1349 '{}.new.dat'.format(self.partition))
1350 ZipWrite(output_zip,
1351 '{}.patch.dat'.format(self.path),
1352 '{}.patch.dat'.format(self.partition),
1353 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001354
Dan Albert8e0178d2015-01-27 15:53:15 -08001355 call = ('block_image_update("{device}", '
1356 'package_extract_file("{partition}.transfer.list"), '
1357 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1358 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001359 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001360
Dan Albert8b72aef2015-03-23 19:13:21 -07001361 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001362 data = source.ReadRangeSet(ranges)
1363 ctx = sha1()
1364
1365 for p in data:
1366 ctx.update(p)
1367
1368 return ctx.hexdigest()
1369
Tao Baoe9b61912015-07-09 17:37:49 -07001370 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1371 """Return the hash value for all zero blocks."""
1372 zero_block = '\x00' * 4096
1373 ctx = sha1()
1374 for _ in range(num_blocks):
1375 ctx.update(zero_block)
1376
1377 return ctx.hexdigest()
1378
Tao Bao5ece99d2015-05-12 11:42:31 -07001379 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1380 # remounting R/W. Will change the checking to a finer-grained way to
1381 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001382 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001383 r = rangelib.RangeSet((0, 1))
1384 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001385
1386 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1387 'abort("%s has been remounted R/W; '
1388 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001389 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001390 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001391
1392DataImage = blockimgdiff.DataImage
1393
1394
Doug Zongker96a57e72010-09-26 14:57:41 -07001395# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001396PARTITION_TYPES = {
1397 "yaffs2": "MTD",
1398 "mtd": "MTD",
1399 "ext4": "EMMC",
1400 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001401 "f2fs": "EMMC",
1402 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001403}
Doug Zongker96a57e72010-09-26 14:57:41 -07001404
1405def GetTypeAndDevice(mount_point, info):
1406 fstab = info["fstab"]
1407 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001408 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1409 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001410 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001411 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001412
1413
1414def ParseCertificate(data):
1415 """Parse a PEM-format certificate."""
1416 cert = []
1417 save = False
1418 for line in data.split("\n"):
1419 if "--END CERTIFICATE--" in line:
1420 break
1421 if save:
1422 cert.append(line)
1423 if "--BEGIN CERTIFICATE--" in line:
1424 save = True
1425 cert = "".join(cert).decode('base64')
1426 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001427
Doug Zongker412c02f2014-02-13 10:58:24 -08001428def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1429 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001430 """Generate a binary patch that creates the recovery image starting
1431 with the boot image. (Most of the space in these images is just the
1432 kernel, which is identical for the two, so the resulting patch
1433 should be efficient.) Add it to the output zip, along with a shell
1434 script that is run from init.rc on first boot to actually do the
1435 patching and install the new recovery image.
1436
1437 recovery_img and boot_img should be File objects for the
1438 corresponding images. info should be the dictionary returned by
1439 common.LoadInfoDict() on the input target_files.
1440 """
1441
Doug Zongker412c02f2014-02-13 10:58:24 -08001442 if info_dict is None:
1443 info_dict = OPTIONS.info_dict
1444
Tao Baof2cffbd2015-07-22 12:33:18 -07001445 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001446 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001447
Tao Baof2cffbd2015-07-22 12:33:18 -07001448 if full_recovery_image:
1449 output_sink("etc/recovery.img", recovery_img.data)
1450
1451 else:
1452 diff_program = ["imgdiff"]
1453 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1454 if os.path.exists(path):
1455 diff_program.append("-b")
1456 diff_program.append(path)
1457 bonus_args = "-b /system/etc/recovery-resource.dat"
1458 else:
1459 bonus_args = ""
1460
1461 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1462 _, _, patch = d.ComputePatch()
1463 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001464
Dan Albertebb19aa2015-03-27 19:11:53 -07001465 try:
1466 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1467 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1468 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001469 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001470
Tao Baof2cffbd2015-07-22 12:33:18 -07001471 if full_recovery_image:
1472 sh = """#!/system/bin/sh
1473if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1474 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"
1475else
1476 log -t recovery "Recovery image already installed"
1477fi
1478""" % {'type': recovery_type,
1479 'device': recovery_device,
1480 'sha1': recovery_img.sha1,
1481 'size': recovery_img.size}
1482 else:
1483 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001484if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1485 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"
1486else
1487 log -t recovery "Recovery image already installed"
1488fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001489""" % {'boot_size': boot_img.size,
1490 'boot_sha1': boot_img.sha1,
1491 'recovery_size': recovery_img.size,
1492 'recovery_sha1': recovery_img.sha1,
1493 'boot_type': boot_type,
1494 'boot_device': boot_device,
1495 'recovery_type': recovery_type,
1496 'recovery_device': recovery_device,
1497 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001498
1499 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001500 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001501 # target-files expects it to be, and put it there.
1502 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001503 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001504 if system_root_image:
1505 init_rc_dir = os.path.join(input_dir, "ROOT")
1506 else:
1507 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001508 init_rc_files = os.listdir(init_rc_dir)
1509 for init_rc_file in init_rc_files:
1510 if (not init_rc_file.startswith('init.') or
1511 not init_rc_file.endswith('.rc')):
1512 continue
1513
1514 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001515 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001516 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001517 if m:
1518 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001519 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001520 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001521
1522 if found:
1523 break
1524
1525 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001526
1527 output_sink(sh_location, sh)