blob: aaf51e9869dda7efbc42e90561c5cf851fe04725 [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
Alex Klyubin9667b182015-12-10 13:38:50 -080047 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.extra_signapk_args = []
49 self.java_path = "java" # Use the one on the path by default.
50 self.java_args = "-Xmx2048m" # JVM Args
51 self.public_key_suffix = ".x509.pem"
52 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070053 # use otatools built boot_signer by default
54 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070055 self.boot_signer_args = []
56 self.verity_signer_path = None
57 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070058 self.verbose = False
59 self.tempfiles = []
60 self.device_specific = None
61 self.extras = {}
62 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070063 self.source_info_dict = None
64 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070065 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070066 # Stash size cannot exceed cache_size * threshold.
67 self.cache_size = None
68 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070069
70
71OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070072
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080073
74# Values for "certificate" in apkcerts that mean special things.
75SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
76
77
Dan Albert8b72aef2015-03-23 19:13:21 -070078class ExternalError(RuntimeError):
79 pass
Doug Zongkereef39442009-04-02 12:14:19 -070080
81
82def Run(args, **kwargs):
83 """Create and return a subprocess.Popen object, printing the command
84 line on the terminal if -v was specified."""
85 if OPTIONS.verbose:
86 print " running: ", " ".join(args)
87 return subprocess.Popen(args, **kwargs)
88
89
Ying Wang7e6d4e42010-12-13 16:25:36 -080090def CloseInheritedPipes():
91 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
92 before doing other work."""
93 if platform.system() != "Darwin":
94 return
95 for d in range(3, 1025):
96 try:
97 stat = os.fstat(d)
98 if stat is not None:
99 pipebit = stat[0] & 0x1000
100 if pipebit != 0:
101 os.close(d)
102 except OSError:
103 pass
104
105
Tao Bao2c15d9e2015-07-09 11:51:16 -0700106def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700107 """Read and parse the META/misc_info.txt key/value pairs from the
108 input target files and return a dict."""
109
Doug Zongkerc9253822014-02-04 12:17:58 -0800110 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700111 if isinstance(input_file, zipfile.ZipFile):
112 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800113 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700114 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800115 try:
116 with open(path) as f:
117 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700118 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800119 if e.errno == errno.ENOENT:
120 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700121 d = {}
122 try:
Michael Runge6e836112014-04-15 17:40:21 -0700123 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700124 except KeyError:
125 # ok if misc_info.txt doesn't exist
126 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700127
Doug Zongker37974732010-09-16 17:44:38 -0700128 # backwards compatibility: These values used to be in their own
129 # files. Look for them, in case we're processing an old
130 # target_files zip.
131
132 if "mkyaffs2_extra_flags" not in d:
133 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700134 d["mkyaffs2_extra_flags"] = read_helper(
135 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700136 except KeyError:
137 # ok if flags don't exist
138 pass
139
140 if "recovery_api_version" not in d:
141 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700142 d["recovery_api_version"] = read_helper(
143 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700144 except KeyError:
145 raise ValueError("can't find recovery API version in input target-files")
146
147 if "tool_extensions" not in d:
148 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800149 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700150 except KeyError:
151 # ok if extensions don't exist
152 pass
153
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800154 if "fstab_version" not in d:
155 d["fstab_version"] = "1"
156
Tao Bao84e75682015-07-19 02:38:53 -0700157 # A few properties are stored as links to the files in the out/ directory.
158 # It works fine with the build system. However, they are no longer available
159 # when (re)generating from target_files zip. If input_dir is not None, we
160 # are doing repacking. Redirect those properties to the actual files in the
161 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700162 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400163 # We carry a copy of file_contexts.bin under META/. If not available,
164 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700165 # to build images than the one running on device, such as when enabling
166 # system_root_image. In that case, we must have the one for image
167 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700168 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
169 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700170 if d.get("system_root_image") == "true":
171 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700172 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700173 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700174 if not os.path.exists(fc_config):
175 fc_config = None
176
177 if fc_config:
178 d["selinux_fc"] = fc_config
179
Tao Bao84e75682015-07-19 02:38:53 -0700180 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
181 if d.get("system_root_image") == "true":
182 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
183 d["ramdisk_fs_config"] = os.path.join(
184 input_dir, "META", "root_filesystem_config.txt")
185
Doug Zongker37974732010-09-16 17:44:38 -0700186 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800187 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700188 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700189 if not line:
190 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700191 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700192 if not value:
193 continue
Doug Zongker37974732010-09-16 17:44:38 -0700194 if name == "blocksize":
195 d[name] = value
196 else:
197 d[name + "_size"] = value
198 except KeyError:
199 pass
200
201 def makeint(key):
202 if key in d:
203 d[key] = int(d[key], 0)
204
205 makeint("recovery_api_version")
206 makeint("blocksize")
207 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700208 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700209 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700210 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700211 makeint("recovery_size")
212 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800213 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700214
Tao Bao48550cc2015-11-19 17:05:46 -0800215 if d.get("no_recovery", False) == "true":
216 d["fstab"] = None
217 else:
218 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
219 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800220 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700221 return d
222
Doug Zongkerc9253822014-02-04 12:17:58 -0800223def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700224 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800225 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700226 except KeyError:
227 print "Warning: could not find SYSTEM/build.prop in %s" % zip
228 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700229 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700230
Michael Runge6e836112014-04-15 17:40:21 -0700231def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700232 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700233 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700234 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700235 if not line or line.startswith("#"):
236 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700237 if "=" in line:
238 name, value = line.split("=", 1)
239 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700240 return d
241
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700242def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700243 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700244 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700245 self.mount_point = mount_point
246 self.fs_type = fs_type
247 self.device = device
248 self.length = length
249 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700250 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700251
252 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800253 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700254 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800255 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700256 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700257
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800258 if fstab_version == 1:
259 d = {}
260 for line in data.split("\n"):
261 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700262 if not line or line.startswith("#"):
263 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800264 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700265 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800266 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800267 options = None
268 if len(pieces) >= 4:
269 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700270 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800271 if len(pieces) >= 5:
272 options = pieces[4]
273 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700274 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800275 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800276 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700277 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700278
Dan Albert8b72aef2015-03-23 19:13:21 -0700279 mount_point = pieces[0]
280 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800281 if options:
282 options = options.split(",")
283 for i in options:
284 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700285 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800286 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700287 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800288
Dan Albert8b72aef2015-03-23 19:13:21 -0700289 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
290 device=pieces[2], length=length,
291 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800292
293 elif fstab_version == 2:
294 d = {}
295 for line in data.split("\n"):
296 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700297 if not line or line.startswith("#"):
298 continue
Tao Bao548eb762015-06-10 12:32:41 -0700299 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800300 pieces = line.split()
301 if len(pieces) != 5:
302 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
303
304 # Ignore entries that are managed by vold
305 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700306 if "voldmanaged=" in options:
307 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800308
309 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700310 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800311 options = options.split(",")
312 for i in options:
313 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700314 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800315 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800316 # Ignore all unknown options in the unified fstab
317 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800318
Tao Bao548eb762015-06-10 12:32:41 -0700319 mount_flags = pieces[3]
320 # Honor the SELinux context if present.
321 context = None
322 for i in mount_flags.split(","):
323 if i.startswith("context="):
324 context = i
325
Dan Albert8b72aef2015-03-23 19:13:21 -0700326 mount_point = pieces[1]
327 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700328 device=pieces[0], length=length,
329 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800330
331 else:
332 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
333
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700334 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700335 # system. Other areas assume system is always at "/system" so point /system
336 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700337 if system_root_image:
338 assert not d.has_key("/system") and d.has_key("/")
339 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700340 return d
341
342
Doug Zongker37974732010-09-16 17:44:38 -0700343def DumpInfoDict(d):
344 for k, v in sorted(d.items()):
345 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700346
Dan Albert8b72aef2015-03-23 19:13:21 -0700347
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700348def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
349 has_ramdisk=False):
350 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700351
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700352 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
353 'sourcedir'), and turn them into a boot image. Return the image data, or
354 None if sourcedir does not appear to contains files for building the
355 requested image."""
356
357 def make_ramdisk():
358 ramdisk_img = tempfile.NamedTemporaryFile()
359
360 if os.access(fs_config_file, os.F_OK):
361 cmd = ["mkbootfs", "-f", fs_config_file,
362 os.path.join(sourcedir, "RAMDISK")]
363 else:
364 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
365 p1 = Run(cmd, stdout=subprocess.PIPE)
366 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
367
368 p2.wait()
369 p1.wait()
370 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
371 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
372
373 return ramdisk_img
374
375 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
376 return None
377
378 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700379 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700380
Doug Zongkerd5131602012-08-02 14:46:42 -0700381 if info_dict is None:
382 info_dict = OPTIONS.info_dict
383
Doug Zongkereef39442009-04-02 12:14:19 -0700384 img = tempfile.NamedTemporaryFile()
385
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700386 if has_ramdisk:
387 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700388
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800389 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
390 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
391
392 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700393
Benoit Fradina45a8682014-07-14 21:00:43 +0200394 fn = os.path.join(sourcedir, "second")
395 if os.access(fn, os.F_OK):
396 cmd.append("--second")
397 cmd.append(fn)
398
Doug Zongker171f1cd2009-06-15 22:36:37 -0700399 fn = os.path.join(sourcedir, "cmdline")
400 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700401 cmd.append("--cmdline")
402 cmd.append(open(fn).read().rstrip("\n"))
403
404 fn = os.path.join(sourcedir, "base")
405 if os.access(fn, os.F_OK):
406 cmd.append("--base")
407 cmd.append(open(fn).read().rstrip("\n"))
408
Ying Wang4de6b5b2010-08-25 14:29:34 -0700409 fn = os.path.join(sourcedir, "pagesize")
410 if os.access(fn, os.F_OK):
411 cmd.append("--pagesize")
412 cmd.append(open(fn).read().rstrip("\n"))
413
Doug Zongkerd5131602012-08-02 14:46:42 -0700414 args = info_dict.get("mkbootimg_args", None)
415 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700416 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700417
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700418 if has_ramdisk:
419 cmd.extend(["--ramdisk", ramdisk_img.name])
420
Tao Baod95e9fd2015-03-29 23:07:41 -0700421 img_unsigned = None
422 if info_dict.get("vboot", None):
423 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700424 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700425 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700426 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700427
428 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700429 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700430 assert p.returncode == 0, "mkbootimg of %s image failed" % (
431 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700432
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100433 if (info_dict.get("boot_signer", None) == "true" and
434 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700435 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700436 cmd = [OPTIONS.boot_signer_path]
437 cmd.extend(OPTIONS.boot_signer_args)
438 cmd.extend([path, img.name,
439 info_dict["verity_key"] + ".pk8",
440 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700441 p = Run(cmd, stdout=subprocess.PIPE)
442 p.communicate()
443 assert p.returncode == 0, "boot_signer of %s image failed" % path
444
Tao Baod95e9fd2015-03-29 23:07:41 -0700445 # Sign the image if vboot is non-empty.
446 elif info_dict.get("vboot", None):
447 path = "/" + os.path.basename(sourcedir).lower()
448 img_keyblock = tempfile.NamedTemporaryFile()
449 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
450 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700451 info_dict["vboot_key"] + ".vbprivk",
452 info_dict["vboot_subkey"] + ".vbprivk",
453 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700454 img.name]
455 p = Run(cmd, stdout=subprocess.PIPE)
456 p.communicate()
457 assert p.returncode == 0, "vboot_signer of %s image failed" % path
458
Tao Baof3282b42015-04-01 11:21:55 -0700459 # Clean up the temp files.
460 img_unsigned.close()
461 img_keyblock.close()
462
Doug Zongkereef39442009-04-02 12:14:19 -0700463 img.seek(os.SEEK_SET, 0)
464 data = img.read()
465
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700466 if has_ramdisk:
467 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700468 img.close()
469
470 return data
471
472
Doug Zongkerd5131602012-08-02 14:46:42 -0700473def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
474 info_dict=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700475 """Return a File object with the desired bootable image.
476
477 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
478 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
479 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700480
Doug Zongker55d93282011-01-25 17:03:34 -0800481 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
482 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700483 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800484 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700485
486 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
487 if os.path.exists(prebuilt_path):
488 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
489 return File.FromLocalFile(name, prebuilt_path)
490
491 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700492
493 if info_dict is None:
494 info_dict = OPTIONS.info_dict
495
496 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800497 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
498 # for recovery.
499 has_ramdisk = (info_dict.get("system_root_image") != "true" or
500 prebuilt_name != "boot.img" or
501 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700502
Doug Zongker6f1d0312014-08-22 08:07:12 -0700503 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700504 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
505 os.path.join(unpack_dir, fs_config),
506 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700507 if data:
508 return File(name, data)
509 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800510
Doug Zongkereef39442009-04-02 12:14:19 -0700511
Doug Zongker75f17362009-12-08 13:46:44 -0800512def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800513 """Unzip the given archive into a temporary directory and return the name.
514
515 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
516 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
517
518 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
519 main file), open for reading.
520 """
Doug Zongkereef39442009-04-02 12:14:19 -0700521
522 tmp = tempfile.mkdtemp(prefix="targetfiles-")
523 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800524
525 def unzip_to_dir(filename, dirname):
526 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
527 if pattern is not None:
528 cmd.append(pattern)
529 p = Run(cmd, stdout=subprocess.PIPE)
530 p.communicate()
531 if p.returncode != 0:
532 raise ExternalError("failed to unzip input target-files \"%s\"" %
533 (filename,))
534
535 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
536 if m:
537 unzip_to_dir(m.group(1), tmp)
538 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
539 filename = m.group(1)
540 else:
541 unzip_to_dir(filename, tmp)
542
543 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700544
545
546def GetKeyPasswords(keylist):
547 """Given a list of keys, prompt the user to enter passwords for
548 those which require them. Return a {key: password} dict. password
549 will be None if the key has no password."""
550
Doug Zongker8ce7c252009-05-22 13:34:54 -0700551 no_passwords = []
552 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700553 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700554 devnull = open("/dev/null", "w+b")
555 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800556 # We don't need a password for things that aren't really keys.
557 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700558 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700559 continue
560
T.R. Fullhart37e10522013-03-18 10:31:26 -0700561 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700562 "-inform", "DER", "-nocrypt"],
563 stdin=devnull.fileno(),
564 stdout=devnull.fileno(),
565 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700566 p.communicate()
567 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700568 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700569 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700570 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700571 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
572 "-inform", "DER", "-passin", "pass:"],
573 stdin=devnull.fileno(),
574 stdout=devnull.fileno(),
575 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700576 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700577 if p.returncode == 0:
578 # Encrypted key with empty string as password.
579 key_passwords[k] = ''
580 elif stderr.startswith('Error decrypting key'):
581 # Definitely encrypted key.
582 # It would have said "Error reading key" if it didn't parse correctly.
583 need_passwords.append(k)
584 else:
585 # Potentially, a type of key that openssl doesn't understand.
586 # We'll let the routines in signapk.jar handle it.
587 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700588 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700589
T.R. Fullhart37e10522013-03-18 10:31:26 -0700590 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700591 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700592 return key_passwords
593
594
Alex Klyubineb756d72015-12-04 09:21:08 -0800595def SignFile(input_name, output_name, key, password, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700596 """Sign the input_name zip/jar/apk, producing output_name. Use the
597 given key and password (the latter may be None if the key does not
598 have a password.
599
Doug Zongker951495f2009-08-14 12:44:19 -0700600 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
Alex Klyubin9667b182015-12-10 13:38:50 -0800605 java_library_path = os.path.join(
606 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
607
608 cmd = [OPTIONS.java_path, OPTIONS.java_args,
609 "-Djava.library.path=" + java_library_path,
610 "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700611 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
612 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700613 if whole_file:
614 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700615 cmd.extend([key + OPTIONS.public_key_suffix,
616 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800617 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700618
619 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700620 if password is not None:
621 password += "\n"
622 p.communicate(password)
623 if p.returncode != 0:
624 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
625
Doug Zongkereef39442009-04-02 12:14:19 -0700626
Doug Zongker37974732010-09-16 17:44:38 -0700627def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700628 """Check the data string passed against the max size limit, if
629 any, for the given target. Raise exception if the data is too big.
630 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700631
Dan Albert8b72aef2015-03-23 19:13:21 -0700632 if target.endswith(".img"):
633 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700634 mount_point = "/" + target
635
Ying Wangf8824af2014-06-03 14:07:27 -0700636 fs_type = None
637 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700638 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700639 if mount_point == "/userdata":
640 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700641 p = info_dict["fstab"][mount_point]
642 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800643 device = p.device
644 if "/" in device:
645 device = device[device.rfind("/")+1:]
646 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700647 if not fs_type or not limit:
648 return
Doug Zongkereef39442009-04-02 12:14:19 -0700649
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700650 if fs_type == "yaffs2":
651 # image size should be increased by 1/64th to account for the
652 # spare area (64 bytes per 2k page)
653 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800654 size = len(data)
655 pct = float(size) * 100.0 / limit
656 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
657 if pct >= 99.0:
658 raise ExternalError(msg)
659 elif pct >= 95.0:
660 print
661 print " WARNING: ", msg
662 print
663 elif OPTIONS.verbose:
664 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700665
666
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800667def ReadApkCerts(tf_zip):
668 """Given a target_files ZipFile, parse the META/apkcerts.txt file
669 and return a {package: cert} dict."""
670 certmap = {}
671 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
672 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700673 if not line:
674 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800675 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
676 r'private_key="(.*)"$', line)
677 if m:
678 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700679 public_key_suffix_len = len(OPTIONS.public_key_suffix)
680 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800681 if cert in SPECIAL_CERT_STRINGS and not privkey:
682 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700683 elif (cert.endswith(OPTIONS.public_key_suffix) and
684 privkey.endswith(OPTIONS.private_key_suffix) and
685 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
686 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800687 else:
688 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
689 return certmap
690
691
Doug Zongkereef39442009-04-02 12:14:19 -0700692COMMON_DOCSTRING = """
693 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700694 Prepend <dir>/bin to the list of places to search for binaries
695 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700696
Doug Zongker05d3dea2009-06-22 11:32:31 -0700697 -s (--device_specific) <file>
698 Path to the python module containing device-specific
699 releasetools code.
700
Doug Zongker8bec09e2009-11-30 15:37:14 -0800701 -x (--extra) <key=value>
702 Add a key/value pair to the 'extras' dict, which device-specific
703 extension code may look at.
704
Doug Zongkereef39442009-04-02 12:14:19 -0700705 -v (--verbose)
706 Show command lines being executed.
707
708 -h (--help)
709 Display this usage message and exit.
710"""
711
712def Usage(docstring):
713 print docstring.rstrip("\n")
714 print COMMON_DOCSTRING
715
716
717def ParseOptions(argv,
718 docstring,
719 extra_opts="", extra_long_opts=(),
720 extra_option_handler=None):
721 """Parse the options in argv and return any arguments that aren't
722 flags. docstring is the calling module's docstring, to be displayed
723 for errors and -h. extra_opts and extra_long_opts are for flags
724 defined by the caller, which are processed by passing them to
725 extra_option_handler."""
726
727 try:
728 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800729 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800730 ["help", "verbose", "path=", "signapk_path=",
731 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700732 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700733 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
734 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800735 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700736 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700737 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700738 Usage(docstring)
739 print "**", str(err), "**"
740 sys.exit(2)
741
Doug Zongkereef39442009-04-02 12:14:19 -0700742 for o, a in opts:
743 if o in ("-h", "--help"):
744 Usage(docstring)
745 sys.exit()
746 elif o in ("-v", "--verbose"):
747 OPTIONS.verbose = True
748 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700749 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700750 elif o in ("--signapk_path",):
751 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800752 elif o in ("--signapk_shared_library_path",):
753 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700754 elif o in ("--extra_signapk_args",):
755 OPTIONS.extra_signapk_args = shlex.split(a)
756 elif o in ("--java_path",):
757 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700758 elif o in ("--java_args",):
759 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700760 elif o in ("--public_key_suffix",):
761 OPTIONS.public_key_suffix = a
762 elif o in ("--private_key_suffix",):
763 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800764 elif o in ("--boot_signer_path",):
765 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700766 elif o in ("--boot_signer_args",):
767 OPTIONS.boot_signer_args = shlex.split(a)
768 elif o in ("--verity_signer_path",):
769 OPTIONS.verity_signer_path = a
770 elif o in ("--verity_signer_args",):
771 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700772 elif o in ("-s", "--device_specific"):
773 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800774 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800775 key, value = a.split("=", 1)
776 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700777 else:
778 if extra_option_handler is None or not extra_option_handler(o, a):
779 assert False, "unknown option \"%s\"" % (o,)
780
Doug Zongker85448772014-09-09 14:59:20 -0700781 if OPTIONS.search_path:
782 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
783 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700784
785 return args
786
787
Doug Zongkerfc44a512014-08-26 13:10:25 -0700788def MakeTempFile(prefix=None, suffix=None):
789 """Make a temp file and add it to the list of things to be deleted
790 when Cleanup() is called. Return the filename."""
791 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
792 os.close(fd)
793 OPTIONS.tempfiles.append(fn)
794 return fn
795
796
Doug Zongkereef39442009-04-02 12:14:19 -0700797def Cleanup():
798 for i in OPTIONS.tempfiles:
799 if os.path.isdir(i):
800 shutil.rmtree(i)
801 else:
802 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700803
804
805class PasswordManager(object):
806 def __init__(self):
807 self.editor = os.getenv("EDITOR", None)
808 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
809
810 def GetPasswords(self, items):
811 """Get passwords corresponding to each string in 'items',
812 returning a dict. (The dict may have keys in addition to the
813 values in 'items'.)
814
815 Uses the passwords in $ANDROID_PW_FILE if available, letting the
816 user edit that file to add more needed passwords. If no editor is
817 available, or $ANDROID_PW_FILE isn't define, prompts the user
818 interactively in the ordinary way.
819 """
820
821 current = self.ReadFile()
822
823 first = True
824 while True:
825 missing = []
826 for i in items:
827 if i not in current or not current[i]:
828 missing.append(i)
829 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700830 if not missing:
831 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700832
833 for i in missing:
834 current[i] = ""
835
836 if not first:
837 print "key file %s still missing some passwords." % (self.pwfile,)
838 answer = raw_input("try to edit again? [y]> ").strip()
839 if answer and answer[0] not in 'yY':
840 raise RuntimeError("key passwords unavailable")
841 first = False
842
843 current = self.UpdateAndReadFile(current)
844
Dan Albert8b72aef2015-03-23 19:13:21 -0700845 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700846 """Prompt the user to enter a value (password) for each key in
847 'current' whose value is fales. Returns a new dict with all the
848 values.
849 """
850 result = {}
851 for k, v in sorted(current.iteritems()):
852 if v:
853 result[k] = v
854 else:
855 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700856 result[k] = getpass.getpass(
857 "Enter password for %s key> " % k).strip()
858 if result[k]:
859 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700860 return result
861
862 def UpdateAndReadFile(self, current):
863 if not self.editor or not self.pwfile:
864 return self.PromptResult(current)
865
866 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700867 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700868 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
869 f.write("# (Additional spaces are harmless.)\n\n")
870
871 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700872 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
873 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700874 f.write("[[[ %s ]]] %s\n" % (v, k))
875 if not v and first_line is None:
876 # position cursor on first line with no password.
877 first_line = i + 4
878 f.close()
879
880 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
881 _, _ = p.communicate()
882
883 return self.ReadFile()
884
885 def ReadFile(self):
886 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700887 if self.pwfile is None:
888 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700889 try:
890 f = open(self.pwfile, "r")
891 for line in f:
892 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700893 if not line or line[0] == '#':
894 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700895 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
896 if not m:
897 print "failed to parse password file: ", line
898 else:
899 result[m.group(2)] = m.group(1)
900 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700901 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700902 if e.errno != errno.ENOENT:
903 print "error reading password file: ", str(e)
904 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700905
906
Dan Albert8e0178d2015-01-27 15:53:15 -0800907def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
908 compress_type=None):
909 import datetime
910
911 # http://b/18015246
912 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
913 # for files larger than 2GiB. We can work around this by adjusting their
914 # limit. Note that `zipfile.writestr()` will not work for strings larger than
915 # 2GiB. The Python interpreter sometimes rejects strings that large (though
916 # it isn't clear to me exactly what circumstances cause this).
917 # `zipfile.write()` must be used directly to work around this.
918 #
919 # This mess can be avoided if we port to python3.
920 saved_zip64_limit = zipfile.ZIP64_LIMIT
921 zipfile.ZIP64_LIMIT = (1 << 32) - 1
922
923 if compress_type is None:
924 compress_type = zip_file.compression
925 if arcname is None:
926 arcname = filename
927
928 saved_stat = os.stat(filename)
929
930 try:
931 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
932 # file to be zipped and reset it when we're done.
933 os.chmod(filename, perms)
934
935 # Use a fixed timestamp so the output is repeatable.
936 epoch = datetime.datetime.fromtimestamp(0)
937 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
938 os.utime(filename, (timestamp, timestamp))
939
940 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
941 finally:
942 os.chmod(filename, saved_stat.st_mode)
943 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
944 zipfile.ZIP64_LIMIT = saved_zip64_limit
945
946
Tao Bao58c1b962015-05-20 09:32:18 -0700947def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700948 compress_type=None):
949 """Wrap zipfile.writestr() function to work around the zip64 limit.
950
951 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
952 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
953 when calling crc32(bytes).
954
955 But it still works fine to write a shorter string into a large zip file.
956 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
957 when we know the string won't be too long.
958 """
959
960 saved_zip64_limit = zipfile.ZIP64_LIMIT
961 zipfile.ZIP64_LIMIT = (1 << 32) - 1
962
963 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
964 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700965 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700966 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700967 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800968 else:
Tao Baof3282b42015-04-01 11:21:55 -0700969 zinfo = zinfo_or_arcname
970
971 # If compress_type is given, it overrides the value in zinfo.
972 if compress_type is not None:
973 zinfo.compress_type = compress_type
974
Tao Bao58c1b962015-05-20 09:32:18 -0700975 # If perms is given, it has a priority.
976 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700977 # If perms doesn't set the file type, mark it as a regular file.
978 if perms & 0o770000 == 0:
979 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -0700980 zinfo.external_attr = perms << 16
981
Tao Baof3282b42015-04-01 11:21:55 -0700982 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700983 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
984
Dan Albert8b72aef2015-03-23 19:13:21 -0700985 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700986 zipfile.ZIP64_LIMIT = saved_zip64_limit
987
988
989def ZipClose(zip_file):
990 # http://b/18015246
991 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
992 # central directory.
993 saved_zip64_limit = zipfile.ZIP64_LIMIT
994 zipfile.ZIP64_LIMIT = (1 << 32) - 1
995
996 zip_file.close()
997
998 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700999
1000
1001class DeviceSpecificParams(object):
1002 module = None
1003 def __init__(self, **kwargs):
1004 """Keyword arguments to the constructor become attributes of this
1005 object, which is passed to all functions in the device-specific
1006 module."""
1007 for k, v in kwargs.iteritems():
1008 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001009 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001010
1011 if self.module is None:
1012 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001013 if not path:
1014 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001015 try:
1016 if os.path.isdir(path):
1017 info = imp.find_module("releasetools", [path])
1018 else:
1019 d, f = os.path.split(path)
1020 b, x = os.path.splitext(f)
1021 if x == ".py":
1022 f = b
1023 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001024 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001025 self.module = imp.load_module("device_specific", *info)
1026 except ImportError:
1027 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001028
1029 def _DoCall(self, function_name, *args, **kwargs):
1030 """Call the named function in the device-specific module, passing
1031 the given args and kwargs. The first argument to the call will be
1032 the DeviceSpecific object itself. If there is no module, or the
1033 module does not define the function, return the value of the
1034 'default' kwarg (which itself defaults to None)."""
1035 if self.module is None or not hasattr(self.module, function_name):
1036 return kwargs.get("default", None)
1037 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1038
1039 def FullOTA_Assertions(self):
1040 """Called after emitting the block of assertions at the top of a
1041 full OTA package. Implementations can add whatever additional
1042 assertions they like."""
1043 return self._DoCall("FullOTA_Assertions")
1044
Doug Zongkere5ff5902012-01-17 10:55:37 -08001045 def FullOTA_InstallBegin(self):
1046 """Called at the start of full OTA installation."""
1047 return self._DoCall("FullOTA_InstallBegin")
1048
Doug Zongker05d3dea2009-06-22 11:32:31 -07001049 def FullOTA_InstallEnd(self):
1050 """Called at the end of full OTA installation; typically this is
1051 used to install the image for the device's baseband processor."""
1052 return self._DoCall("FullOTA_InstallEnd")
1053
1054 def IncrementalOTA_Assertions(self):
1055 """Called after emitting the block of assertions at the top of an
1056 incremental OTA package. Implementations can add whatever
1057 additional assertions they like."""
1058 return self._DoCall("IncrementalOTA_Assertions")
1059
Doug Zongkere5ff5902012-01-17 10:55:37 -08001060 def IncrementalOTA_VerifyBegin(self):
1061 """Called at the start of the verification phase of incremental
1062 OTA installation; additional checks can be placed here to abort
1063 the script before any changes are made."""
1064 return self._DoCall("IncrementalOTA_VerifyBegin")
1065
Doug Zongker05d3dea2009-06-22 11:32:31 -07001066 def IncrementalOTA_VerifyEnd(self):
1067 """Called at the end of the verification phase of incremental OTA
1068 installation; additional checks can be placed here to abort the
1069 script before any changes are made."""
1070 return self._DoCall("IncrementalOTA_VerifyEnd")
1071
Doug Zongkere5ff5902012-01-17 10:55:37 -08001072 def IncrementalOTA_InstallBegin(self):
1073 """Called at the start of incremental OTA installation (after
1074 verification is complete)."""
1075 return self._DoCall("IncrementalOTA_InstallBegin")
1076
Doug Zongker05d3dea2009-06-22 11:32:31 -07001077 def IncrementalOTA_InstallEnd(self):
1078 """Called at the end of incremental OTA installation; typically
1079 this is used to install the image for the device's baseband
1080 processor."""
1081 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001082
Tao Bao9bc6bb22015-11-09 16:58:28 -08001083 def VerifyOTA_Assertions(self):
1084 return self._DoCall("VerifyOTA_Assertions")
1085
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001086class File(object):
1087 def __init__(self, name, data):
1088 self.name = name
1089 self.data = data
1090 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001091 self.sha1 = sha1(data).hexdigest()
1092
1093 @classmethod
1094 def FromLocalFile(cls, name, diskname):
1095 f = open(diskname, "rb")
1096 data = f.read()
1097 f.close()
1098 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001099
1100 def WriteToTemp(self):
1101 t = tempfile.NamedTemporaryFile()
1102 t.write(self.data)
1103 t.flush()
1104 return t
1105
Geremy Condra36bd3652014-02-06 19:45:10 -08001106 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001107 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001108
1109DIFF_PROGRAM_BY_EXT = {
1110 ".gz" : "imgdiff",
1111 ".zip" : ["imgdiff", "-z"],
1112 ".jar" : ["imgdiff", "-z"],
1113 ".apk" : ["imgdiff", "-z"],
1114 ".img" : "imgdiff",
1115 }
1116
1117class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001118 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001119 self.tf = tf
1120 self.sf = sf
1121 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001122 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001123
1124 def ComputePatch(self):
1125 """Compute the patch (as a string of data) needed to turn sf into
1126 tf. Returns the same tuple as GetPatch()."""
1127
1128 tf = self.tf
1129 sf = self.sf
1130
Doug Zongker24cd2802012-08-14 16:36:15 -07001131 if self.diff_program:
1132 diff_program = self.diff_program
1133 else:
1134 ext = os.path.splitext(tf.name)[1]
1135 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001136
1137 ttemp = tf.WriteToTemp()
1138 stemp = sf.WriteToTemp()
1139
1140 ext = os.path.splitext(tf.name)[1]
1141
1142 try:
1143 ptemp = tempfile.NamedTemporaryFile()
1144 if isinstance(diff_program, list):
1145 cmd = copy.copy(diff_program)
1146 else:
1147 cmd = [diff_program]
1148 cmd.append(stemp.name)
1149 cmd.append(ttemp.name)
1150 cmd.append(ptemp.name)
1151 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001152 err = []
1153 def run():
1154 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001155 if e:
1156 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001157 th = threading.Thread(target=run)
1158 th.start()
1159 th.join(timeout=300) # 5 mins
1160 if th.is_alive():
1161 print "WARNING: diff command timed out"
1162 p.terminate()
1163 th.join(5)
1164 if th.is_alive():
1165 p.kill()
1166 th.join()
1167
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001168 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001169 print "WARNING: failure running %s:\n%s\n" % (
1170 diff_program, "".join(err))
1171 self.patch = None
1172 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001173 diff = ptemp.read()
1174 finally:
1175 ptemp.close()
1176 stemp.close()
1177 ttemp.close()
1178
1179 self.patch = diff
1180 return self.tf, self.sf, self.patch
1181
1182
1183 def GetPatch(self):
1184 """Return a tuple (target_file, source_file, patch_data).
1185 patch_data may be None if ComputePatch hasn't been called, or if
1186 computing the patch failed."""
1187 return self.tf, self.sf, self.patch
1188
1189
1190def ComputeDifferences(diffs):
1191 """Call ComputePatch on all the Difference objects in 'diffs'."""
1192 print len(diffs), "diffs to compute"
1193
1194 # Do the largest files first, to try and reduce the long-pole effect.
1195 by_size = [(i.tf.size, i) for i in diffs]
1196 by_size.sort(reverse=True)
1197 by_size = [i[1] for i in by_size]
1198
1199 lock = threading.Lock()
1200 diff_iter = iter(by_size) # accessed under lock
1201
1202 def worker():
1203 try:
1204 lock.acquire()
1205 for d in diff_iter:
1206 lock.release()
1207 start = time.time()
1208 d.ComputePatch()
1209 dur = time.time() - start
1210 lock.acquire()
1211
1212 tf, sf, patch = d.GetPatch()
1213 if sf.name == tf.name:
1214 name = tf.name
1215 else:
1216 name = "%s (%s)" % (tf.name, sf.name)
1217 if patch is None:
1218 print "patching failed! %s" % (name,)
1219 else:
1220 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1221 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1222 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001223 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001224 print e
1225 raise
1226
1227 # start worker threads; wait for them all to finish.
1228 threads = [threading.Thread(target=worker)
1229 for i in range(OPTIONS.worker_threads)]
1230 for th in threads:
1231 th.start()
1232 while threads:
1233 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001234
1235
Dan Albert8b72aef2015-03-23 19:13:21 -07001236class BlockDifference(object):
1237 def __init__(self, partition, tgt, src=None, check_first_block=False,
1238 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001239 self.tgt = tgt
1240 self.src = src
1241 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001242 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001243
Tao Bao5ece99d2015-05-12 11:42:31 -07001244 # Due to http://b/20939131, check_first_block is disabled temporarily.
1245 assert not self.check_first_block
1246
Tao Baodd2a5892015-03-12 12:32:37 -07001247 if version is None:
1248 version = 1
1249 if OPTIONS.info_dict:
1250 version = max(
1251 int(i) for i in
1252 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1253 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001254
1255 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001256 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001257 tmpdir = tempfile.mkdtemp()
1258 OPTIONS.tempfiles.append(tmpdir)
1259 self.path = os.path.join(tmpdir, partition)
1260 b.Compute(self.path)
1261
Tao Baoaac4ad52015-10-16 15:26:34 -07001262 if src is None:
1263 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1264 else:
1265 _, self.device = GetTypeAndDevice("/" + partition,
1266 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001267
1268 def WriteScript(self, script, output_zip, progress=None):
1269 if not self.src:
1270 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001271 script.Print("Patching %s image unconditionally..." % (self.partition,))
1272 else:
1273 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001274
Dan Albert8b72aef2015-03-23 19:13:21 -07001275 if progress:
1276 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001277 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001278 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001279
Tao Bao9bc6bb22015-11-09 16:58:28 -08001280 def WriteStrictVerifyScript(self, script):
1281 """Verify all the blocks in the care_map, including clobbered blocks.
1282
1283 This differs from the WriteVerifyScript() function: a) it prints different
1284 error messages; b) it doesn't allow half-way updated images to pass the
1285 verification."""
1286
1287 partition = self.partition
1288 script.Print("Verifying %s..." % (partition,))
1289 ranges = self.tgt.care_map
1290 ranges_str = ranges.to_string_raw()
1291 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1292 'ui_print(" Verified.") || '
1293 'ui_print("\\"%s\\" has unexpected contents.");' % (
1294 self.device, ranges_str,
1295 self.tgt.TotalSha1(include_clobbered_blocks=True),
1296 self.device))
1297 script.AppendExtra("")
1298
Jesse Zhao75bcea02015-01-06 10:59:53 -08001299 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001300 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001301 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001302 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001303 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001304 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1305 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001306 if self.version >= 4:
1307 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1308 'block_image_verify("%s", '
1309 'package_extract_file("%s.transfer.list"), '
1310 '"%s.new.dat", "%s.patch.dat") || '
1311 '(block_image_recover("%s", "%s") && '
1312 'block_image_verify("%s", '
1313 'package_extract_file("%s.transfer.list"), '
1314 '"%s.new.dat", "%s.patch.dat"))) then') % (
1315 self.device, ranges_str, self.src.TotalSha1(),
1316 self.device, partition, partition, partition,
1317 self.device, ranges_str,
1318 self.device, partition, partition, partition))
1319 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001320 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1321 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001322 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001323 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001324 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001325 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001326 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001327 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001328 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001329 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001330 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001331
Tao Baodd2a5892015-03-12 12:32:37 -07001332 # When generating incrementals for the system and vendor partitions,
1333 # explicitly check the first block (which contains the superblock) of
1334 # the partition to see if it's what we expect. If this check fails,
1335 # give an explicit log message about the partition having been
1336 # remounted R/W (the most likely explanation) and the need to flash to
1337 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001338 if self.check_first_block:
1339 self._CheckFirstBlock(script)
1340
Tao Baodd2a5892015-03-12 12:32:37 -07001341 # Abort the OTA update. Note that the incremental OTA cannot be applied
1342 # even if it may match the checksum of the target partition.
1343 # a) If version < 3, operations like move and erase will make changes
1344 # unconditionally and damage the partition.
1345 # b) If version >= 3, it won't even reach here.
1346 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1347 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001348
Tao Bao5fcaaef2015-06-01 13:40:49 -07001349 def _WritePostInstallVerifyScript(self, script):
1350 partition = self.partition
1351 script.Print('Verifying the updated %s image...' % (partition,))
1352 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1353 ranges = self.tgt.care_map
1354 ranges_str = ranges.to_string_raw()
1355 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1356 self.device, ranges_str,
1357 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001358
1359 # Bug: 20881595
1360 # Verify that extended blocks are really zeroed out.
1361 if self.tgt.extended:
1362 ranges_str = self.tgt.extended.to_string_raw()
1363 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1364 self.device, ranges_str,
1365 self._HashZeroBlocks(self.tgt.extended.size())))
1366 script.Print('Verified the updated %s image.' % (partition,))
1367 script.AppendExtra(
1368 'else\n'
1369 ' abort("%s partition has unexpected non-zero contents after OTA '
1370 'update");\n'
1371 'endif;' % (partition,))
1372 else:
1373 script.Print('Verified the updated %s image.' % (partition,))
1374
Tao Bao5fcaaef2015-06-01 13:40:49 -07001375 script.AppendExtra(
1376 'else\n'
1377 ' abort("%s partition has unexpected contents after OTA update");\n'
1378 'endif;' % (partition,))
1379
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001380 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001381 ZipWrite(output_zip,
1382 '{}.transfer.list'.format(self.path),
1383 '{}.transfer.list'.format(self.partition))
1384 ZipWrite(output_zip,
1385 '{}.new.dat'.format(self.path),
1386 '{}.new.dat'.format(self.partition))
1387 ZipWrite(output_zip,
1388 '{}.patch.dat'.format(self.path),
1389 '{}.patch.dat'.format(self.partition),
1390 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001391
Dan Albert8e0178d2015-01-27 15:53:15 -08001392 call = ('block_image_update("{device}", '
1393 'package_extract_file("{partition}.transfer.list"), '
1394 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1395 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001396 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001397
Dan Albert8b72aef2015-03-23 19:13:21 -07001398 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001399 data = source.ReadRangeSet(ranges)
1400 ctx = sha1()
1401
1402 for p in data:
1403 ctx.update(p)
1404
1405 return ctx.hexdigest()
1406
Tao Baoe9b61912015-07-09 17:37:49 -07001407 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1408 """Return the hash value for all zero blocks."""
1409 zero_block = '\x00' * 4096
1410 ctx = sha1()
1411 for _ in range(num_blocks):
1412 ctx.update(zero_block)
1413
1414 return ctx.hexdigest()
1415
Tao Bao5ece99d2015-05-12 11:42:31 -07001416 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1417 # remounting R/W. Will change the checking to a finer-grained way to
1418 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001419 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001420 r = rangelib.RangeSet((0, 1))
1421 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001422
1423 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1424 'abort("%s has been remounted R/W; '
1425 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001426 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001427 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001428
1429DataImage = blockimgdiff.DataImage
1430
1431
Doug Zongker96a57e72010-09-26 14:57:41 -07001432# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001433PARTITION_TYPES = {
1434 "yaffs2": "MTD",
1435 "mtd": "MTD",
1436 "ext4": "EMMC",
1437 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001438 "f2fs": "EMMC",
1439 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001440}
Doug Zongker96a57e72010-09-26 14:57:41 -07001441
1442def GetTypeAndDevice(mount_point, info):
1443 fstab = info["fstab"]
1444 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001445 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1446 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001447 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001448 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001449
1450
1451def ParseCertificate(data):
1452 """Parse a PEM-format certificate."""
1453 cert = []
1454 save = False
1455 for line in data.split("\n"):
1456 if "--END CERTIFICATE--" in line:
1457 break
1458 if save:
1459 cert.append(line)
1460 if "--BEGIN CERTIFICATE--" in line:
1461 save = True
1462 cert = "".join(cert).decode('base64')
1463 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001464
Doug Zongker412c02f2014-02-13 10:58:24 -08001465def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1466 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001467 """Generate a binary patch that creates the recovery image starting
1468 with the boot image. (Most of the space in these images is just the
1469 kernel, which is identical for the two, so the resulting patch
1470 should be efficient.) Add it to the output zip, along with a shell
1471 script that is run from init.rc on first boot to actually do the
1472 patching and install the new recovery image.
1473
1474 recovery_img and boot_img should be File objects for the
1475 corresponding images. info should be the dictionary returned by
1476 common.LoadInfoDict() on the input target_files.
1477 """
1478
Doug Zongker412c02f2014-02-13 10:58:24 -08001479 if info_dict is None:
1480 info_dict = OPTIONS.info_dict
1481
Tao Baof2cffbd2015-07-22 12:33:18 -07001482 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001483 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001484
Tao Baof2cffbd2015-07-22 12:33:18 -07001485 if full_recovery_image:
1486 output_sink("etc/recovery.img", recovery_img.data)
1487
1488 else:
1489 diff_program = ["imgdiff"]
1490 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1491 if os.path.exists(path):
1492 diff_program.append("-b")
1493 diff_program.append(path)
1494 bonus_args = "-b /system/etc/recovery-resource.dat"
1495 else:
1496 bonus_args = ""
1497
1498 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1499 _, _, patch = d.ComputePatch()
1500 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001501
Dan Albertebb19aa2015-03-27 19:11:53 -07001502 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001503 # The following GetTypeAndDevice()s need to use the path in the target
1504 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001505 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1506 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1507 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001508 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001509
Tao Baof2cffbd2015-07-22 12:33:18 -07001510 if full_recovery_image:
1511 sh = """#!/system/bin/sh
1512if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1513 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"
1514else
1515 log -t recovery "Recovery image already installed"
1516fi
1517""" % {'type': recovery_type,
1518 'device': recovery_device,
1519 'sha1': recovery_img.sha1,
1520 'size': recovery_img.size}
1521 else:
1522 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001523if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1524 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"
1525else
1526 log -t recovery "Recovery image already installed"
1527fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001528""" % {'boot_size': boot_img.size,
1529 'boot_sha1': boot_img.sha1,
1530 'recovery_size': recovery_img.size,
1531 'recovery_sha1': recovery_img.sha1,
1532 'boot_type': boot_type,
1533 'boot_device': boot_device,
1534 'recovery_type': recovery_type,
1535 'recovery_device': recovery_device,
1536 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001537
1538 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001539 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001540 # target-files expects it to be, and put it there.
1541 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001542 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001543 if system_root_image:
1544 init_rc_dir = os.path.join(input_dir, "ROOT")
1545 else:
1546 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001547 init_rc_files = os.listdir(init_rc_dir)
1548 for init_rc_file in init_rc_files:
1549 if (not init_rc_file.startswith('init.') or
1550 not init_rc_file.endswith('.rc')):
1551 continue
1552
1553 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001554 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001555 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001556 if m:
1557 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001558 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001559 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001560
1561 if found:
1562 break
1563
1564 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001565
1566 output_sink(sh_location, sh)