blob: 664a7d3e0992b532e8cd251b5a4de977c69c6840 [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 Klyubinc2a36af2016-01-13 10:32:47 -0800595def GetMinSdkVersion(apk_name):
596 """Get the minSdkVersion delared in the APK. This can be both a decimal number
597 (API Level) or a codename.
598 """
599
600 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
601 output, err = p.communicate()
602 if err:
603 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
604 % (p.returncode,))
605
606 for line in output.split("\n"):
607 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
608 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
609 if m:
610 return m.group(1)
611 raise ExternalError("No minSdkVersion returned by aapt")
612
613
614def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
615 """Get the minSdkVersion declared in the APK as a number (API Level). If
616 minSdkVersion is set to a codename, it is translated to a number using the
617 provided map.
618 """
619
620 version = GetMinSdkVersion(apk_name)
621 try:
622 return int(version)
623 except ValueError:
624 # Not a decimal number. Codename?
625 if version in codename_to_api_level_map:
626 return codename_to_api_level_map[version]
627 else:
628 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
629 % (version, codename_to_api_level_map))
630
631
632def SignFile(input_name, output_name, key, password, min_api_level=None,
633 codename_to_api_level_map=dict(),
634 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700635 """Sign the input_name zip/jar/apk, producing output_name. Use the
636 given key and password (the latter may be None if the key does not
637 have a password.
638
Doug Zongker951495f2009-08-14 12:44:19 -0700639 If whole_file is true, use the "-w" option to SignApk to embed a
640 signature that covers the whole file in the archive comment of the
641 zip file.
Alex Klyubinc2a36af2016-01-13 10:32:47 -0800642
643 min_api_level is the API Level (int) of the oldest platform this file may end
644 up on. If not specified for an APK, the API Level is obtained by interpreting
645 the minSdkVersion attribute of the APK's AndroidManifest.xml.
646
647 codename_to_api_level_map is needed to translate the codename which may be
648 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700649 """
Doug Zongker951495f2009-08-14 12:44:19 -0700650
Alex Klyubin9667b182015-12-10 13:38:50 -0800651 java_library_path = os.path.join(
652 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
653
654 cmd = [OPTIONS.java_path, OPTIONS.java_args,
655 "-Djava.library.path=" + java_library_path,
656 "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700657 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
658 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700659 if whole_file:
660 cmd.append("-w")
Alex Klyubinc2a36af2016-01-13 10:32:47 -0800661
662 min_sdk_version = min_api_level
663 if min_sdk_version is None:
664 if not whole_file:
665 min_sdk_version = GetMinSdkVersionInt(
666 input_name, codename_to_api_level_map)
667 if min_sdk_version is not None:
668 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
669
T.R. Fullhart37e10522013-03-18 10:31:26 -0700670 cmd.extend([key + OPTIONS.public_key_suffix,
671 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800672 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700673
674 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700675 if password is not None:
676 password += "\n"
677 p.communicate(password)
678 if p.returncode != 0:
679 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
680
Doug Zongkereef39442009-04-02 12:14:19 -0700681
Doug Zongker37974732010-09-16 17:44:38 -0700682def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700683 """Check the data string passed against the max size limit, if
684 any, for the given target. Raise exception if the data is too big.
685 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700686
Dan Albert8b72aef2015-03-23 19:13:21 -0700687 if target.endswith(".img"):
688 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700689 mount_point = "/" + target
690
Ying Wangf8824af2014-06-03 14:07:27 -0700691 fs_type = None
692 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700693 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700694 if mount_point == "/userdata":
695 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700696 p = info_dict["fstab"][mount_point]
697 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800698 device = p.device
699 if "/" in device:
700 device = device[device.rfind("/")+1:]
701 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700702 if not fs_type or not limit:
703 return
Doug Zongkereef39442009-04-02 12:14:19 -0700704
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700705 if fs_type == "yaffs2":
706 # image size should be increased by 1/64th to account for the
707 # spare area (64 bytes per 2k page)
708 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800709 size = len(data)
710 pct = float(size) * 100.0 / limit
711 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
712 if pct >= 99.0:
713 raise ExternalError(msg)
714 elif pct >= 95.0:
715 print
716 print " WARNING: ", msg
717 print
718 elif OPTIONS.verbose:
719 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700720
721
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800722def ReadApkCerts(tf_zip):
723 """Given a target_files ZipFile, parse the META/apkcerts.txt file
724 and return a {package: cert} dict."""
725 certmap = {}
726 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
727 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700728 if not line:
729 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800730 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
731 r'private_key="(.*)"$', line)
732 if m:
733 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700734 public_key_suffix_len = len(OPTIONS.public_key_suffix)
735 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800736 if cert in SPECIAL_CERT_STRINGS and not privkey:
737 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700738 elif (cert.endswith(OPTIONS.public_key_suffix) and
739 privkey.endswith(OPTIONS.private_key_suffix) and
740 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
741 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800742 else:
743 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
744 return certmap
745
746
Doug Zongkereef39442009-04-02 12:14:19 -0700747COMMON_DOCSTRING = """
748 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700749 Prepend <dir>/bin to the list of places to search for binaries
750 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700751
Doug Zongker05d3dea2009-06-22 11:32:31 -0700752 -s (--device_specific) <file>
753 Path to the python module containing device-specific
754 releasetools code.
755
Doug Zongker8bec09e2009-11-30 15:37:14 -0800756 -x (--extra) <key=value>
757 Add a key/value pair to the 'extras' dict, which device-specific
758 extension code may look at.
759
Doug Zongkereef39442009-04-02 12:14:19 -0700760 -v (--verbose)
761 Show command lines being executed.
762
763 -h (--help)
764 Display this usage message and exit.
765"""
766
767def Usage(docstring):
768 print docstring.rstrip("\n")
769 print COMMON_DOCSTRING
770
771
772def ParseOptions(argv,
773 docstring,
774 extra_opts="", extra_long_opts=(),
775 extra_option_handler=None):
776 """Parse the options in argv and return any arguments that aren't
777 flags. docstring is the calling module's docstring, to be displayed
778 for errors and -h. extra_opts and extra_long_opts are for flags
779 defined by the caller, which are processed by passing them to
780 extra_option_handler."""
781
782 try:
783 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800784 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800785 ["help", "verbose", "path=", "signapk_path=",
786 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700787 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700788 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
789 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800790 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700791 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700792 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700793 Usage(docstring)
794 print "**", str(err), "**"
795 sys.exit(2)
796
Doug Zongkereef39442009-04-02 12:14:19 -0700797 for o, a in opts:
798 if o in ("-h", "--help"):
799 Usage(docstring)
800 sys.exit()
801 elif o in ("-v", "--verbose"):
802 OPTIONS.verbose = True
803 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700804 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700805 elif o in ("--signapk_path",):
806 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800807 elif o in ("--signapk_shared_library_path",):
808 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700809 elif o in ("--extra_signapk_args",):
810 OPTIONS.extra_signapk_args = shlex.split(a)
811 elif o in ("--java_path",):
812 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700813 elif o in ("--java_args",):
814 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700815 elif o in ("--public_key_suffix",):
816 OPTIONS.public_key_suffix = a
817 elif o in ("--private_key_suffix",):
818 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800819 elif o in ("--boot_signer_path",):
820 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700821 elif o in ("--boot_signer_args",):
822 OPTIONS.boot_signer_args = shlex.split(a)
823 elif o in ("--verity_signer_path",):
824 OPTIONS.verity_signer_path = a
825 elif o in ("--verity_signer_args",):
826 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700827 elif o in ("-s", "--device_specific"):
828 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800829 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800830 key, value = a.split("=", 1)
831 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700832 else:
833 if extra_option_handler is None or not extra_option_handler(o, a):
834 assert False, "unknown option \"%s\"" % (o,)
835
Doug Zongker85448772014-09-09 14:59:20 -0700836 if OPTIONS.search_path:
837 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
838 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700839
840 return args
841
842
Doug Zongkerfc44a512014-08-26 13:10:25 -0700843def MakeTempFile(prefix=None, suffix=None):
844 """Make a temp file and add it to the list of things to be deleted
845 when Cleanup() is called. Return the filename."""
846 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
847 os.close(fd)
848 OPTIONS.tempfiles.append(fn)
849 return fn
850
851
Doug Zongkereef39442009-04-02 12:14:19 -0700852def Cleanup():
853 for i in OPTIONS.tempfiles:
854 if os.path.isdir(i):
855 shutil.rmtree(i)
856 else:
857 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700858
859
860class PasswordManager(object):
861 def __init__(self):
862 self.editor = os.getenv("EDITOR", None)
863 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
864
865 def GetPasswords(self, items):
866 """Get passwords corresponding to each string in 'items',
867 returning a dict. (The dict may have keys in addition to the
868 values in 'items'.)
869
870 Uses the passwords in $ANDROID_PW_FILE if available, letting the
871 user edit that file to add more needed passwords. If no editor is
872 available, or $ANDROID_PW_FILE isn't define, prompts the user
873 interactively in the ordinary way.
874 """
875
876 current = self.ReadFile()
877
878 first = True
879 while True:
880 missing = []
881 for i in items:
882 if i not in current or not current[i]:
883 missing.append(i)
884 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700885 if not missing:
886 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700887
888 for i in missing:
889 current[i] = ""
890
891 if not first:
892 print "key file %s still missing some passwords." % (self.pwfile,)
893 answer = raw_input("try to edit again? [y]> ").strip()
894 if answer and answer[0] not in 'yY':
895 raise RuntimeError("key passwords unavailable")
896 first = False
897
898 current = self.UpdateAndReadFile(current)
899
Dan Albert8b72aef2015-03-23 19:13:21 -0700900 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700901 """Prompt the user to enter a value (password) for each key in
902 'current' whose value is fales. Returns a new dict with all the
903 values.
904 """
905 result = {}
906 for k, v in sorted(current.iteritems()):
907 if v:
908 result[k] = v
909 else:
910 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700911 result[k] = getpass.getpass(
912 "Enter password for %s key> " % k).strip()
913 if result[k]:
914 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700915 return result
916
917 def UpdateAndReadFile(self, current):
918 if not self.editor or not self.pwfile:
919 return self.PromptResult(current)
920
921 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700922 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700923 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
924 f.write("# (Additional spaces are harmless.)\n\n")
925
926 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700927 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
928 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700929 f.write("[[[ %s ]]] %s\n" % (v, k))
930 if not v and first_line is None:
931 # position cursor on first line with no password.
932 first_line = i + 4
933 f.close()
934
935 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
936 _, _ = p.communicate()
937
938 return self.ReadFile()
939
940 def ReadFile(self):
941 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700942 if self.pwfile is None:
943 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700944 try:
945 f = open(self.pwfile, "r")
946 for line in f:
947 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700948 if not line or line[0] == '#':
949 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700950 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
951 if not m:
952 print "failed to parse password file: ", line
953 else:
954 result[m.group(2)] = m.group(1)
955 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700956 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700957 if e.errno != errno.ENOENT:
958 print "error reading password file: ", str(e)
959 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700960
961
Dan Albert8e0178d2015-01-27 15:53:15 -0800962def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
963 compress_type=None):
964 import datetime
965
966 # http://b/18015246
967 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
968 # for files larger than 2GiB. We can work around this by adjusting their
969 # limit. Note that `zipfile.writestr()` will not work for strings larger than
970 # 2GiB. The Python interpreter sometimes rejects strings that large (though
971 # it isn't clear to me exactly what circumstances cause this).
972 # `zipfile.write()` must be used directly to work around this.
973 #
974 # This mess can be avoided if we port to python3.
975 saved_zip64_limit = zipfile.ZIP64_LIMIT
976 zipfile.ZIP64_LIMIT = (1 << 32) - 1
977
978 if compress_type is None:
979 compress_type = zip_file.compression
980 if arcname is None:
981 arcname = filename
982
983 saved_stat = os.stat(filename)
984
985 try:
986 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
987 # file to be zipped and reset it when we're done.
988 os.chmod(filename, perms)
989
990 # Use a fixed timestamp so the output is repeatable.
991 epoch = datetime.datetime.fromtimestamp(0)
992 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
993 os.utime(filename, (timestamp, timestamp))
994
995 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
996 finally:
997 os.chmod(filename, saved_stat.st_mode)
998 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
999 zipfile.ZIP64_LIMIT = saved_zip64_limit
1000
1001
Tao Bao58c1b962015-05-20 09:32:18 -07001002def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001003 compress_type=None):
1004 """Wrap zipfile.writestr() function to work around the zip64 limit.
1005
1006 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1007 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1008 when calling crc32(bytes).
1009
1010 But it still works fine to write a shorter string into a large zip file.
1011 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1012 when we know the string won't be too long.
1013 """
1014
1015 saved_zip64_limit = zipfile.ZIP64_LIMIT
1016 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1017
1018 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1019 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001020 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001021 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001022 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001023 else:
Tao Baof3282b42015-04-01 11:21:55 -07001024 zinfo = zinfo_or_arcname
1025
1026 # If compress_type is given, it overrides the value in zinfo.
1027 if compress_type is not None:
1028 zinfo.compress_type = compress_type
1029
Tao Bao58c1b962015-05-20 09:32:18 -07001030 # If perms is given, it has a priority.
1031 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001032 # If perms doesn't set the file type, mark it as a regular file.
1033 if perms & 0o770000 == 0:
1034 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001035 zinfo.external_attr = perms << 16
1036
Tao Baof3282b42015-04-01 11:21:55 -07001037 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001038 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1039
Dan Albert8b72aef2015-03-23 19:13:21 -07001040 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001041 zipfile.ZIP64_LIMIT = saved_zip64_limit
1042
1043
1044def ZipClose(zip_file):
1045 # http://b/18015246
1046 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1047 # central directory.
1048 saved_zip64_limit = zipfile.ZIP64_LIMIT
1049 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1050
1051 zip_file.close()
1052
1053 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001054
1055
1056class DeviceSpecificParams(object):
1057 module = None
1058 def __init__(self, **kwargs):
1059 """Keyword arguments to the constructor become attributes of this
1060 object, which is passed to all functions in the device-specific
1061 module."""
1062 for k, v in kwargs.iteritems():
1063 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001064 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001065
1066 if self.module is None:
1067 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001068 if not path:
1069 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001070 try:
1071 if os.path.isdir(path):
1072 info = imp.find_module("releasetools", [path])
1073 else:
1074 d, f = os.path.split(path)
1075 b, x = os.path.splitext(f)
1076 if x == ".py":
1077 f = b
1078 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001079 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001080 self.module = imp.load_module("device_specific", *info)
1081 except ImportError:
1082 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001083
1084 def _DoCall(self, function_name, *args, **kwargs):
1085 """Call the named function in the device-specific module, passing
1086 the given args and kwargs. The first argument to the call will be
1087 the DeviceSpecific object itself. If there is no module, or the
1088 module does not define the function, return the value of the
1089 'default' kwarg (which itself defaults to None)."""
1090 if self.module is None or not hasattr(self.module, function_name):
1091 return kwargs.get("default", None)
1092 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1093
1094 def FullOTA_Assertions(self):
1095 """Called after emitting the block of assertions at the top of a
1096 full OTA package. Implementations can add whatever additional
1097 assertions they like."""
1098 return self._DoCall("FullOTA_Assertions")
1099
Doug Zongkere5ff5902012-01-17 10:55:37 -08001100 def FullOTA_InstallBegin(self):
1101 """Called at the start of full OTA installation."""
1102 return self._DoCall("FullOTA_InstallBegin")
1103
Doug Zongker05d3dea2009-06-22 11:32:31 -07001104 def FullOTA_InstallEnd(self):
1105 """Called at the end of full OTA installation; typically this is
1106 used to install the image for the device's baseband processor."""
1107 return self._DoCall("FullOTA_InstallEnd")
1108
1109 def IncrementalOTA_Assertions(self):
1110 """Called after emitting the block of assertions at the top of an
1111 incremental OTA package. Implementations can add whatever
1112 additional assertions they like."""
1113 return self._DoCall("IncrementalOTA_Assertions")
1114
Doug Zongkere5ff5902012-01-17 10:55:37 -08001115 def IncrementalOTA_VerifyBegin(self):
1116 """Called at the start of the verification phase of incremental
1117 OTA installation; additional checks can be placed here to abort
1118 the script before any changes are made."""
1119 return self._DoCall("IncrementalOTA_VerifyBegin")
1120
Doug Zongker05d3dea2009-06-22 11:32:31 -07001121 def IncrementalOTA_VerifyEnd(self):
1122 """Called at the end of the verification phase of incremental OTA
1123 installation; additional checks can be placed here to abort the
1124 script before any changes are made."""
1125 return self._DoCall("IncrementalOTA_VerifyEnd")
1126
Doug Zongkere5ff5902012-01-17 10:55:37 -08001127 def IncrementalOTA_InstallBegin(self):
1128 """Called at the start of incremental OTA installation (after
1129 verification is complete)."""
1130 return self._DoCall("IncrementalOTA_InstallBegin")
1131
Doug Zongker05d3dea2009-06-22 11:32:31 -07001132 def IncrementalOTA_InstallEnd(self):
1133 """Called at the end of incremental OTA installation; typically
1134 this is used to install the image for the device's baseband
1135 processor."""
1136 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001137
Tao Bao9bc6bb22015-11-09 16:58:28 -08001138 def VerifyOTA_Assertions(self):
1139 return self._DoCall("VerifyOTA_Assertions")
1140
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001141class File(object):
1142 def __init__(self, name, data):
1143 self.name = name
1144 self.data = data
1145 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001146 self.sha1 = sha1(data).hexdigest()
1147
1148 @classmethod
1149 def FromLocalFile(cls, name, diskname):
1150 f = open(diskname, "rb")
1151 data = f.read()
1152 f.close()
1153 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001154
1155 def WriteToTemp(self):
1156 t = tempfile.NamedTemporaryFile()
1157 t.write(self.data)
1158 t.flush()
1159 return t
1160
Geremy Condra36bd3652014-02-06 19:45:10 -08001161 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001162 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001163
1164DIFF_PROGRAM_BY_EXT = {
1165 ".gz" : "imgdiff",
1166 ".zip" : ["imgdiff", "-z"],
1167 ".jar" : ["imgdiff", "-z"],
1168 ".apk" : ["imgdiff", "-z"],
1169 ".img" : "imgdiff",
1170 }
1171
1172class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001173 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001174 self.tf = tf
1175 self.sf = sf
1176 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001177 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001178
1179 def ComputePatch(self):
1180 """Compute the patch (as a string of data) needed to turn sf into
1181 tf. Returns the same tuple as GetPatch()."""
1182
1183 tf = self.tf
1184 sf = self.sf
1185
Doug Zongker24cd2802012-08-14 16:36:15 -07001186 if self.diff_program:
1187 diff_program = self.diff_program
1188 else:
1189 ext = os.path.splitext(tf.name)[1]
1190 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001191
1192 ttemp = tf.WriteToTemp()
1193 stemp = sf.WriteToTemp()
1194
1195 ext = os.path.splitext(tf.name)[1]
1196
1197 try:
1198 ptemp = tempfile.NamedTemporaryFile()
1199 if isinstance(diff_program, list):
1200 cmd = copy.copy(diff_program)
1201 else:
1202 cmd = [diff_program]
1203 cmd.append(stemp.name)
1204 cmd.append(ttemp.name)
1205 cmd.append(ptemp.name)
1206 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001207 err = []
1208 def run():
1209 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001210 if e:
1211 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001212 th = threading.Thread(target=run)
1213 th.start()
1214 th.join(timeout=300) # 5 mins
1215 if th.is_alive():
1216 print "WARNING: diff command timed out"
1217 p.terminate()
1218 th.join(5)
1219 if th.is_alive():
1220 p.kill()
1221 th.join()
1222
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001223 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001224 print "WARNING: failure running %s:\n%s\n" % (
1225 diff_program, "".join(err))
1226 self.patch = None
1227 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001228 diff = ptemp.read()
1229 finally:
1230 ptemp.close()
1231 stemp.close()
1232 ttemp.close()
1233
1234 self.patch = diff
1235 return self.tf, self.sf, self.patch
1236
1237
1238 def GetPatch(self):
1239 """Return a tuple (target_file, source_file, patch_data).
1240 patch_data may be None if ComputePatch hasn't been called, or if
1241 computing the patch failed."""
1242 return self.tf, self.sf, self.patch
1243
1244
1245def ComputeDifferences(diffs):
1246 """Call ComputePatch on all the Difference objects in 'diffs'."""
1247 print len(diffs), "diffs to compute"
1248
1249 # Do the largest files first, to try and reduce the long-pole effect.
1250 by_size = [(i.tf.size, i) for i in diffs]
1251 by_size.sort(reverse=True)
1252 by_size = [i[1] for i in by_size]
1253
1254 lock = threading.Lock()
1255 diff_iter = iter(by_size) # accessed under lock
1256
1257 def worker():
1258 try:
1259 lock.acquire()
1260 for d in diff_iter:
1261 lock.release()
1262 start = time.time()
1263 d.ComputePatch()
1264 dur = time.time() - start
1265 lock.acquire()
1266
1267 tf, sf, patch = d.GetPatch()
1268 if sf.name == tf.name:
1269 name = tf.name
1270 else:
1271 name = "%s (%s)" % (tf.name, sf.name)
1272 if patch is None:
1273 print "patching failed! %s" % (name,)
1274 else:
1275 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1276 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1277 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001278 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001279 print e
1280 raise
1281
1282 # start worker threads; wait for them all to finish.
1283 threads = [threading.Thread(target=worker)
1284 for i in range(OPTIONS.worker_threads)]
1285 for th in threads:
1286 th.start()
1287 while threads:
1288 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001289
1290
Dan Albert8b72aef2015-03-23 19:13:21 -07001291class BlockDifference(object):
1292 def __init__(self, partition, tgt, src=None, check_first_block=False,
1293 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001294 self.tgt = tgt
1295 self.src = src
1296 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001297 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001298
Tao Baodd2a5892015-03-12 12:32:37 -07001299 if version is None:
1300 version = 1
1301 if OPTIONS.info_dict:
1302 version = max(
1303 int(i) for i in
1304 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1305 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001306
1307 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001308 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001309 tmpdir = tempfile.mkdtemp()
1310 OPTIONS.tempfiles.append(tmpdir)
1311 self.path = os.path.join(tmpdir, partition)
1312 b.Compute(self.path)
1313
Tao Baoaac4ad52015-10-16 15:26:34 -07001314 if src is None:
1315 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1316 else:
1317 _, self.device = GetTypeAndDevice("/" + partition,
1318 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001319
1320 def WriteScript(self, script, output_zip, progress=None):
1321 if not self.src:
1322 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001323 script.Print("Patching %s image unconditionally..." % (self.partition,))
1324 else:
1325 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001326
Dan Albert8b72aef2015-03-23 19:13:21 -07001327 if progress:
1328 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001329 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001330 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001331
Tao Bao9bc6bb22015-11-09 16:58:28 -08001332 def WriteStrictVerifyScript(self, script):
1333 """Verify all the blocks in the care_map, including clobbered blocks.
1334
1335 This differs from the WriteVerifyScript() function: a) it prints different
1336 error messages; b) it doesn't allow half-way updated images to pass the
1337 verification."""
1338
1339 partition = self.partition
1340 script.Print("Verifying %s..." % (partition,))
1341 ranges = self.tgt.care_map
1342 ranges_str = ranges.to_string_raw()
1343 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1344 'ui_print(" Verified.") || '
1345 'ui_print("\\"%s\\" has unexpected contents.");' % (
1346 self.device, ranges_str,
1347 self.tgt.TotalSha1(include_clobbered_blocks=True),
1348 self.device))
1349 script.AppendExtra("")
1350
Jesse Zhao75bcea02015-01-06 10:59:53 -08001351 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001352 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001353 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001354 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001355 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001356 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1357 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001358 if self.version >= 4:
1359 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1360 'block_image_verify("%s", '
1361 'package_extract_file("%s.transfer.list"), '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001362 '"%s.new.dat", "%s.patch.dat")) then') % (
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001363 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001364 self.device, partition, partition, partition))
1365 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001366 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1367 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001368 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001369 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001370 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001371 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001372 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001373 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001374 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001375 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001376 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001377
Tianjie Xufc3422a2015-12-15 11:53:59 -08001378 if self.version >= 4:
1379
1380 # Bug: 21124327
1381 # When generating incrementals for the system and vendor partitions in
1382 # version 4 or newer, explicitly check the first block (which contains
1383 # the superblock) of the partition to see if it's what we expect. If
1384 # this check fails, give an explicit log message about the partition
1385 # having been remounted R/W (the most likely explanation).
1386 if self.check_first_block:
1387 script.AppendExtra('check_first_block("%s");' % (self.device,))
1388
1389 # If version >= 4, try block recovery before abort update
1390 script.AppendExtra((
1391 'ifelse (block_image_recover("{device}", "{ranges}") && '
1392 'block_image_verify("{device}", '
1393 'package_extract_file("{partition}.transfer.list"), '
1394 '"{partition}.new.dat", "{partition}.patch.dat"), '
1395 'ui_print("{partition} recovered successfully."), '
1396 'abort("{partition} partition fails to recover"));\n'
1397 'endif;').format(device=self.device, ranges=ranges_str,
1398 partition=partition))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001399
Tao Baodd2a5892015-03-12 12:32:37 -07001400 # Abort the OTA update. Note that the incremental OTA cannot be applied
1401 # even if it may match the checksum of the target partition.
1402 # a) If version < 3, operations like move and erase will make changes
1403 # unconditionally and damage the partition.
1404 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001405 else:
1406 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1407 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001408
Tao Bao5fcaaef2015-06-01 13:40:49 -07001409 def _WritePostInstallVerifyScript(self, script):
1410 partition = self.partition
1411 script.Print('Verifying the updated %s image...' % (partition,))
1412 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1413 ranges = self.tgt.care_map
1414 ranges_str = ranges.to_string_raw()
1415 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1416 self.device, ranges_str,
1417 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001418
1419 # Bug: 20881595
1420 # Verify that extended blocks are really zeroed out.
1421 if self.tgt.extended:
1422 ranges_str = self.tgt.extended.to_string_raw()
1423 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1424 self.device, ranges_str,
1425 self._HashZeroBlocks(self.tgt.extended.size())))
1426 script.Print('Verified the updated %s image.' % (partition,))
1427 script.AppendExtra(
1428 'else\n'
1429 ' abort("%s partition has unexpected non-zero contents after OTA '
1430 'update");\n'
1431 'endif;' % (partition,))
1432 else:
1433 script.Print('Verified the updated %s image.' % (partition,))
1434
Tao Bao5fcaaef2015-06-01 13:40:49 -07001435 script.AppendExtra(
1436 'else\n'
1437 ' abort("%s partition has unexpected contents after OTA update");\n'
1438 'endif;' % (partition,))
1439
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001440 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001441 ZipWrite(output_zip,
1442 '{}.transfer.list'.format(self.path),
1443 '{}.transfer.list'.format(self.partition))
1444 ZipWrite(output_zip,
1445 '{}.new.dat'.format(self.path),
1446 '{}.new.dat'.format(self.partition))
1447 ZipWrite(output_zip,
1448 '{}.patch.dat'.format(self.path),
1449 '{}.patch.dat'.format(self.partition),
1450 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001451
Dan Albert8e0178d2015-01-27 15:53:15 -08001452 call = ('block_image_update("{device}", '
1453 'package_extract_file("{partition}.transfer.list"), '
1454 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1455 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001456 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001457
Dan Albert8b72aef2015-03-23 19:13:21 -07001458 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001459 data = source.ReadRangeSet(ranges)
1460 ctx = sha1()
1461
1462 for p in data:
1463 ctx.update(p)
1464
1465 return ctx.hexdigest()
1466
Tao Baoe9b61912015-07-09 17:37:49 -07001467 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1468 """Return the hash value for all zero blocks."""
1469 zero_block = '\x00' * 4096
1470 ctx = sha1()
1471 for _ in range(num_blocks):
1472 ctx.update(zero_block)
1473
1474 return ctx.hexdigest()
1475
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001476
1477DataImage = blockimgdiff.DataImage
1478
Doug Zongker96a57e72010-09-26 14:57:41 -07001479# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001480PARTITION_TYPES = {
1481 "yaffs2": "MTD",
1482 "mtd": "MTD",
1483 "ext4": "EMMC",
1484 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001485 "f2fs": "EMMC",
1486 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001487}
Doug Zongker96a57e72010-09-26 14:57:41 -07001488
1489def GetTypeAndDevice(mount_point, info):
1490 fstab = info["fstab"]
1491 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001492 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1493 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001494 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001495 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001496
1497
1498def ParseCertificate(data):
1499 """Parse a PEM-format certificate."""
1500 cert = []
1501 save = False
1502 for line in data.split("\n"):
1503 if "--END CERTIFICATE--" in line:
1504 break
1505 if save:
1506 cert.append(line)
1507 if "--BEGIN CERTIFICATE--" in line:
1508 save = True
1509 cert = "".join(cert).decode('base64')
1510 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001511
Doug Zongker412c02f2014-02-13 10:58:24 -08001512def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1513 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001514 """Generate a binary patch that creates the recovery image starting
1515 with the boot image. (Most of the space in these images is just the
1516 kernel, which is identical for the two, so the resulting patch
1517 should be efficient.) Add it to the output zip, along with a shell
1518 script that is run from init.rc on first boot to actually do the
1519 patching and install the new recovery image.
1520
1521 recovery_img and boot_img should be File objects for the
1522 corresponding images. info should be the dictionary returned by
1523 common.LoadInfoDict() on the input target_files.
1524 """
1525
Doug Zongker412c02f2014-02-13 10:58:24 -08001526 if info_dict is None:
1527 info_dict = OPTIONS.info_dict
1528
Tao Baof2cffbd2015-07-22 12:33:18 -07001529 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001530 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001531
Tao Baof2cffbd2015-07-22 12:33:18 -07001532 if full_recovery_image:
1533 output_sink("etc/recovery.img", recovery_img.data)
1534
1535 else:
1536 diff_program = ["imgdiff"]
1537 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1538 if os.path.exists(path):
1539 diff_program.append("-b")
1540 diff_program.append(path)
1541 bonus_args = "-b /system/etc/recovery-resource.dat"
1542 else:
1543 bonus_args = ""
1544
1545 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1546 _, _, patch = d.ComputePatch()
1547 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001548
Dan Albertebb19aa2015-03-27 19:11:53 -07001549 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001550 # The following GetTypeAndDevice()s need to use the path in the target
1551 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001552 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1553 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1554 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001555 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001556
Tao Baof2cffbd2015-07-22 12:33:18 -07001557 if full_recovery_image:
1558 sh = """#!/system/bin/sh
1559if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1560 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"
1561else
1562 log -t recovery "Recovery image already installed"
1563fi
1564""" % {'type': recovery_type,
1565 'device': recovery_device,
1566 'sha1': recovery_img.sha1,
1567 'size': recovery_img.size}
1568 else:
1569 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001570if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1571 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"
1572else
1573 log -t recovery "Recovery image already installed"
1574fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001575""" % {'boot_size': boot_img.size,
1576 'boot_sha1': boot_img.sha1,
1577 'recovery_size': recovery_img.size,
1578 'recovery_sha1': recovery_img.sha1,
1579 'boot_type': boot_type,
1580 'boot_device': boot_device,
1581 'recovery_type': recovery_type,
1582 'recovery_device': recovery_device,
1583 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001584
1585 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001586 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001587 # target-files expects it to be, and put it there.
1588 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001589 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001590 if system_root_image:
1591 init_rc_dir = os.path.join(input_dir, "ROOT")
1592 else:
1593 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001594 init_rc_files = os.listdir(init_rc_dir)
1595 for init_rc_file in init_rc_files:
1596 if (not init_rc_file.startswith('init.') or
1597 not init_rc_file.endswith('.rc')):
1598 continue
1599
1600 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001601 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001602 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001603 if m:
1604 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001605 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001606 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001607
1608 if found:
1609 break
1610
1611 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001612
1613 output_sink(sh_location, sh)