blob: 38c73fd6e73b3e768b9586b59ab36b0d2e5a41f8 [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
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Doug Zongkerea5d7a92010-09-12 15:26:16 -070017import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070018import errno
Doug Zongkereef39442009-04-02 12:14:19 -070019import getopt
20import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010021import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070022import imp
Doug Zongkereef39442009-04-02 12:14:19 -070023import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080024import platform
Doug Zongkereef39442009-04-02 12:14:19 -070025import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070026import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070027import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080028import string
Doug Zongkereef39442009-04-02 12:14:19 -070029import subprocess
30import sys
31import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070032import threading
33import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070034import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080035from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070036
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070037import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080038import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070039
Dan Albert8b72aef2015-03-23 19:13:21 -070040class Options(object):
41 def __init__(self):
42 platform_search_path = {
43 "linux2": "out/host/linux-x86",
44 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070045 }
Doug Zongker85448772014-09-09 14:59:20 -070046
Tao Bao76def242017-11-21 09:25:31 -080047 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080049 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070050 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080052 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070053 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070055 # use otatools built boot_signer by default
56 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070057 self.boot_signer_args = []
58 self.verity_signer_path = None
59 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.verbose = False
61 self.tempfiles = []
62 self.device_specific = None
63 self.extras = {}
64 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070065 self.source_info_dict = None
66 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070068 # Stash size cannot exceed cache_size * threshold.
69 self.cache_size = None
70 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070071
72
73OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070074
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080075
76# Values for "certificate" in apkcerts that mean special things.
77SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
78
Tao Bao9dd909e2017-11-14 11:27:32 -080079
80# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010081AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010082 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080083
84
Tianjie Xu209db462016-05-24 17:34:52 -070085class ErrorCode(object):
86 """Define error_codes for failures that happen during the actual
87 update package installation.
88
89 Error codes 0-999 are reserved for failures before the package
90 installation (i.e. low battery, package verification failure).
91 Detailed code in 'bootable/recovery/error_code.h' """
92
93 SYSTEM_VERIFICATION_FAILURE = 1000
94 SYSTEM_UPDATE_FAILURE = 1001
95 SYSTEM_UNEXPECTED_CONTENTS = 1002
96 SYSTEM_NONZERO_CONTENTS = 1003
97 SYSTEM_RECOVER_FAILURE = 1004
98 VENDOR_VERIFICATION_FAILURE = 2000
99 VENDOR_UPDATE_FAILURE = 2001
100 VENDOR_UNEXPECTED_CONTENTS = 2002
101 VENDOR_NONZERO_CONTENTS = 2003
102 VENDOR_RECOVER_FAILURE = 2004
103 OEM_PROP_MISMATCH = 3000
104 FINGERPRINT_MISMATCH = 3001
105 THUMBPRINT_MISMATCH = 3002
106 OLDER_BUILD = 3003
107 DEVICE_MISMATCH = 3004
108 BAD_PATCH_FILE = 3005
109 INSUFFICIENT_CACHE_SPACE = 3006
110 TUNE_PARTITION_FAILURE = 3007
111 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800112
Tao Bao80921982018-03-21 21:02:19 -0700113
Dan Albert8b72aef2015-03-23 19:13:21 -0700114class ExternalError(RuntimeError):
115 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700116
117
Tao Bao39451582017-05-04 11:10:47 -0700118def Run(args, verbose=None, **kwargs):
119 """Create and return a subprocess.Popen object.
120
121 Caller can specify if the command line should be printed. The global
122 OPTIONS.verbose will be used if not specified.
123 """
124 if verbose is None:
125 verbose = OPTIONS.verbose
126 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800127 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700128 return subprocess.Popen(args, **kwargs)
129
130
Tao Baoc765cca2018-01-31 17:32:40 -0800131def RoundUpTo4K(value):
132 rounded_up = value + 4095
133 return rounded_up - (rounded_up % 4096)
134
135
Ying Wang7e6d4e42010-12-13 16:25:36 -0800136def CloseInheritedPipes():
137 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
138 before doing other work."""
139 if platform.system() != "Darwin":
140 return
141 for d in range(3, 1025):
142 try:
143 stat = os.fstat(d)
144 if stat is not None:
145 pipebit = stat[0] & 0x1000
146 if pipebit != 0:
147 os.close(d)
148 except OSError:
149 pass
150
151
Tao Bao2c15d9e2015-07-09 11:51:16 -0700152def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700153 """Read and parse the META/misc_info.txt key/value pairs from the
154 input target files and return a dict."""
155
Doug Zongkerc9253822014-02-04 12:17:58 -0800156 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700157 if isinstance(input_file, zipfile.ZipFile):
158 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800159 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700160 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800161 try:
162 with open(path) as f:
163 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700164 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800165 if e.errno == errno.ENOENT:
166 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800167
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700168 try:
Michael Runge6e836112014-04-15 17:40:21 -0700169 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700170 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800171 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700172
Tao Bao6cd54732017-02-27 15:12:05 -0800173 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800174 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800175
Tao Bao84e75682015-07-19 02:38:53 -0700176 # A few properties are stored as links to the files in the out/ directory.
177 # It works fine with the build system. However, they are no longer available
178 # when (re)generating from target_files zip. If input_dir is not None, we
179 # are doing repacking. Redirect those properties to the actual files in the
180 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700181 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400182 # We carry a copy of file_contexts.bin under META/. If not available,
183 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700184 # to build images than the one running on device, such as when enabling
185 # system_root_image. In that case, we must have the one for image
186 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700187 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
188 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700189 if d.get("system_root_image") == "true":
190 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700191 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700192 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700193 if not os.path.exists(fc_config):
194 fc_config = None
195
196 if fc_config:
197 d["selinux_fc"] = fc_config
198
Tao Bao8bfd3c72018-07-20 15:20:28 -0700199 # Similarly we need to redirect "root_dir" and "root_fs_config".
Tao Bao84e75682015-07-19 02:38:53 -0700200 if d.get("system_root_image") == "true":
Tao Bao8bfd3c72018-07-20 15:20:28 -0700201 d["root_dir"] = os.path.join(input_dir, "ROOT")
202 d["root_fs_config"] = os.path.join(
Tao Bao84e75682015-07-19 02:38:53 -0700203 input_dir, "META", "root_filesystem_config.txt")
204
Tao Baof54216f2016-03-29 15:12:37 -0700205 # Redirect {system,vendor}_base_fs_file.
206 if "system_base_fs_file" in d:
207 basename = os.path.basename(d["system_base_fs_file"])
208 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700209 if os.path.exists(system_base_fs_file):
210 d["system_base_fs_file"] = system_base_fs_file
211 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800212 print("Warning: failed to find system base fs file: %s" % (
213 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700214 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700215
216 if "vendor_base_fs_file" in d:
217 basename = os.path.basename(d["vendor_base_fs_file"])
218 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700219 if os.path.exists(vendor_base_fs_file):
220 d["vendor_base_fs_file"] = vendor_base_fs_file
221 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800222 print("Warning: failed to find vendor base fs file: %s" % (
223 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700224 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700225
Doug Zongker37974732010-09-16 17:44:38 -0700226 def makeint(key):
227 if key in d:
228 d[key] = int(d[key], 0)
229
230 makeint("recovery_api_version")
231 makeint("blocksize")
232 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700233 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700234 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700235 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700236 makeint("recovery_size")
237 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800238 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700239
Tao Bao76def242017-11-21 09:25:31 -0800240 system_root_image = d.get("system_root_image") == "true"
241 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700242 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700243 if isinstance(input_file, zipfile.ZipFile):
244 if recovery_fstab_path not in input_file.namelist():
245 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
246 else:
247 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
248 if not os.path.exists(path):
249 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800250 d["fstab"] = LoadRecoveryFSTab(
251 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700252
Tao Bao76def242017-11-21 09:25:31 -0800253 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700254 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700255 if isinstance(input_file, zipfile.ZipFile):
256 if recovery_fstab_path not in input_file.namelist():
257 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
258 else:
259 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
260 if not os.path.exists(path):
261 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800262 d["fstab"] = LoadRecoveryFSTab(
263 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700264
Tianjie Xucfa86222016-03-07 16:31:19 -0800265 else:
266 d["fstab"] = None
267
Tao Baobcd1d162017-08-26 13:10:26 -0700268 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
269 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Tao Bao12d87fc2018-01-31 12:18:52 -0800270
271 # Set up the salt (based on fingerprint or thumbprint) that will be used when
272 # adding AVB footer.
273 if d.get("avb_enable") == "true":
274 fp = None
275 if "build.prop" in d:
276 build_prop = d["build.prop"]
277 if "ro.build.fingerprint" in build_prop:
278 fp = build_prop["ro.build.fingerprint"]
279 elif "ro.build.thumbprint" in build_prop:
280 fp = build_prop["ro.build.thumbprint"]
281 if fp:
282 d["avb_salt"] = sha256(fp).hexdigest()
283
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700284 return d
285
Tao Baod1de6f32017-03-01 16:38:48 -0800286
Tao Baobcd1d162017-08-26 13:10:26 -0700287def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700288 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700289 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700290 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700291 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700292 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700293 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700294
Tao Baod1de6f32017-03-01 16:38:48 -0800295
Michael Runge6e836112014-04-15 17:40:21 -0700296def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700297 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700298 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700299 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700300 if not line or line.startswith("#"):
301 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700302 if "=" in line:
303 name, value = line.split("=", 1)
304 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700305 return d
306
Tao Baod1de6f32017-03-01 16:38:48 -0800307
Tianjie Xucfa86222016-03-07 16:31:19 -0800308def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
309 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700310 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800311 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700312 self.mount_point = mount_point
313 self.fs_type = fs_type
314 self.device = device
315 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700316 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700317
318 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800319 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700320 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800321 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700322 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700323
Tao Baod1de6f32017-03-01 16:38:48 -0800324 assert fstab_version == 2
325
326 d = {}
327 for line in data.split("\n"):
328 line = line.strip()
329 if not line or line.startswith("#"):
330 continue
331
332 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
333 pieces = line.split()
334 if len(pieces) != 5:
335 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
336
337 # Ignore entries that are managed by vold.
338 options = pieces[4]
339 if "voldmanaged=" in options:
340 continue
341
342 # It's a good line, parse it.
343 length = 0
344 options = options.split(",")
345 for i in options:
346 if i.startswith("length="):
347 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800348 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800349 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700350 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800351
Tao Baod1de6f32017-03-01 16:38:48 -0800352 mount_flags = pieces[3]
353 # Honor the SELinux context if present.
354 context = None
355 for i in mount_flags.split(","):
356 if i.startswith("context="):
357 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800358
Tao Baod1de6f32017-03-01 16:38:48 -0800359 mount_point = pieces[1]
360 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
361 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800362
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700363 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700364 # system. Other areas assume system is always at "/system" so point /system
365 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700366 if system_root_image:
367 assert not d.has_key("/system") and d.has_key("/")
368 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700369 return d
370
371
Doug Zongker37974732010-09-16 17:44:38 -0700372def DumpInfoDict(d):
373 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800374 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700375
Dan Albert8b72aef2015-03-23 19:13:21 -0700376
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800377def AppendAVBSigningArgs(cmd, partition):
378 """Append signing arguments for avbtool."""
379 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
380 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
381 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
382 if key_path and algorithm:
383 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700384 avb_salt = OPTIONS.info_dict.get("avb_salt")
385 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
386 if avb_salt and partition != "vbmeta":
387 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800388
389
Tao Bao02a08592018-07-22 12:40:45 -0700390def GetAvbChainedPartitionArg(partition, info_dict, key=None):
391 """Constructs and returns the arg to build or verify a chained partition.
392
393 Args:
394 partition: The partition name.
395 info_dict: The info dict to look up the key info and rollback index
396 location.
397 key: The key to be used for building or verifying the partition. Defaults to
398 the key listed in info_dict.
399
400 Returns:
401 A string of form "partition:rollback_index_location:key" that can be used to
402 build or verify vbmeta image.
403
404 Raises:
405 AssertionError: When it fails to extract the public key with avbtool.
406 """
407 if key is None:
408 key = info_dict["avb_" + partition + "_key_path"]
409 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
410 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
411 proc = Run(
412 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path],
413 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
414 stdoutdata, _ = proc.communicate()
415 assert proc.returncode == 0, \
416 "Failed to extract pubkey for {}:\n{}".format(
417 partition, stdoutdata)
418
419 rollback_index_location = info_dict[
420 "avb_" + partition + "_rollback_index_location"]
421 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
422
423
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700424def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800425 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700426 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700427
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700428 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800429 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
430 we are building a two-step special image (i.e. building a recovery image to
431 be loaded into /boot in two-step OTAs).
432
433 Return the image data, or None if sourcedir does not appear to contains files
434 for building the requested image.
435 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700436
437 def make_ramdisk():
438 ramdisk_img = tempfile.NamedTemporaryFile()
439
440 if os.access(fs_config_file, os.F_OK):
441 cmd = ["mkbootfs", "-f", fs_config_file,
442 os.path.join(sourcedir, "RAMDISK")]
443 else:
444 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
445 p1 = Run(cmd, stdout=subprocess.PIPE)
446 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
447
448 p2.wait()
449 p1.wait()
450 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
451 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
452
453 return ramdisk_img
454
455 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
456 return None
457
458 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700459 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700460
Doug Zongkerd5131602012-08-02 14:46:42 -0700461 if info_dict is None:
462 info_dict = OPTIONS.info_dict
463
Doug Zongkereef39442009-04-02 12:14:19 -0700464 img = tempfile.NamedTemporaryFile()
465
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700466 if has_ramdisk:
467 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700468
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800469 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
470 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
471
472 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700473
Benoit Fradina45a8682014-07-14 21:00:43 +0200474 fn = os.path.join(sourcedir, "second")
475 if os.access(fn, os.F_OK):
476 cmd.append("--second")
477 cmd.append(fn)
478
Doug Zongker171f1cd2009-06-15 22:36:37 -0700479 fn = os.path.join(sourcedir, "cmdline")
480 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700481 cmd.append("--cmdline")
482 cmd.append(open(fn).read().rstrip("\n"))
483
484 fn = os.path.join(sourcedir, "base")
485 if os.access(fn, os.F_OK):
486 cmd.append("--base")
487 cmd.append(open(fn).read().rstrip("\n"))
488
Ying Wang4de6b5b2010-08-25 14:29:34 -0700489 fn = os.path.join(sourcedir, "pagesize")
490 if os.access(fn, os.F_OK):
491 cmd.append("--pagesize")
492 cmd.append(open(fn).read().rstrip("\n"))
493
Tao Bao76def242017-11-21 09:25:31 -0800494 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700495 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700496 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700497
Tao Bao76def242017-11-21 09:25:31 -0800498 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000499 if args and args.strip():
500 cmd.extend(shlex.split(args))
501
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700502 if has_ramdisk:
503 cmd.extend(["--ramdisk", ramdisk_img.name])
504
Tao Baod95e9fd2015-03-29 23:07:41 -0700505 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800506 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700507 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700508 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700509 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700510 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700511
Tao Baobf70c312017-07-11 17:27:55 -0700512 # "boot" or "recovery", without extension.
513 partition_name = os.path.basename(sourcedir).lower()
514
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700515 if (partition_name == "recovery" and
516 info_dict.get("include_recovery_dtbo") == "true"):
517 fn = os.path.join(sourcedir, "recovery_dtbo")
518 cmd.extend(["--recovery_dtbo", fn])
519
Doug Zongker38a649f2009-06-17 09:07:09 -0700520 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700521 p.communicate()
Tao Baobf70c312017-07-11 17:27:55 -0700522 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700523
Tao Bao76def242017-11-21 09:25:31 -0800524 if (info_dict.get("boot_signer") == "true" and
525 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800526 # Hard-code the path as "/boot" for two-step special recovery image (which
527 # will be loaded into /boot during the two-step OTA).
528 if two_step_image:
529 path = "/boot"
530 else:
Tao Baobf70c312017-07-11 17:27:55 -0700531 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700532 cmd = [OPTIONS.boot_signer_path]
533 cmd.extend(OPTIONS.boot_signer_args)
534 cmd.extend([path, img.name,
535 info_dict["verity_key"] + ".pk8",
536 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700537 p = Run(cmd, stdout=subprocess.PIPE)
538 p.communicate()
539 assert p.returncode == 0, "boot_signer of %s image failed" % path
540
Tao Baod95e9fd2015-03-29 23:07:41 -0700541 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800542 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700543 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700544 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800545 # We have switched from the prebuilt futility binary to using the tool
546 # (futility-host) built from the source. Override the setting in the old
547 # TF.zip.
548 futility = info_dict["futility"]
549 if futility.startswith("prebuilts/"):
550 futility = "futility-host"
551 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700552 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700553 info_dict["vboot_key"] + ".vbprivk",
554 info_dict["vboot_subkey"] + ".vbprivk",
555 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700556 img.name]
557 p = Run(cmd, stdout=subprocess.PIPE)
558 p.communicate()
559 assert p.returncode == 0, "vboot_signer of %s image failed" % path
560
Tao Baof3282b42015-04-01 11:21:55 -0700561 # Clean up the temp files.
562 img_unsigned.close()
563 img_keyblock.close()
564
David Zeuthen8fecb282017-12-01 16:24:01 -0500565 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800566 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700567 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500568 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400569 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700570 "--partition_size", str(part_size), "--partition_name",
571 partition_name]
572 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500573 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400574 if args and args.strip():
575 cmd.extend(shlex.split(args))
576 p = Run(cmd, stdout=subprocess.PIPE)
577 p.communicate()
578 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c312017-07-11 17:27:55 -0700579 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500580
581 img.seek(os.SEEK_SET, 0)
582 data = img.read()
583
584 if has_ramdisk:
585 ramdisk_img.close()
586 img.close()
587
588 return data
589
590
Doug Zongkerd5131602012-08-02 14:46:42 -0700591def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800592 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700593 """Return a File object with the desired bootable image.
594
595 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
596 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
597 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700598
Doug Zongker55d93282011-01-25 17:03:34 -0800599 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
600 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800601 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800602 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700603
604 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
605 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800606 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700607 return File.FromLocalFile(name, prebuilt_path)
608
Tao Bao89fbb0f2017-01-10 10:47:58 -0800609 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700610
611 if info_dict is None:
612 info_dict = OPTIONS.info_dict
613
614 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800615 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
616 # for recovery.
617 has_ramdisk = (info_dict.get("system_root_image") != "true" or
618 prebuilt_name != "boot.img" or
619 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700620
Doug Zongker6f1d0312014-08-22 08:07:12 -0700621 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400622 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
623 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800624 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700625 if data:
626 return File(name, data)
627 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800628
Doug Zongkereef39442009-04-02 12:14:19 -0700629
Narayan Kamatha07bf042017-08-14 14:49:21 +0100630def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800631 """Gunzips the given gzip compressed file to a given output file."""
632 with gzip.open(in_filename, "rb") as in_file, \
633 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100634 shutil.copyfileobj(in_file, out_file)
635
636
Doug Zongker75f17362009-12-08 13:46:44 -0800637def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800638 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800639
Tao Bao1c830bf2017-12-25 10:43:47 -0800640 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
641 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800642
Tao Bao1c830bf2017-12-25 10:43:47 -0800643 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800644 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800645 """
Doug Zongkereef39442009-04-02 12:14:19 -0700646
Doug Zongker55d93282011-01-25 17:03:34 -0800647 def unzip_to_dir(filename, dirname):
648 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
649 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800650 cmd.extend(pattern)
Tao Bao80921982018-03-21 21:02:19 -0700651 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
652 stdoutdata, _ = p.communicate()
Doug Zongker55d93282011-01-25 17:03:34 -0800653 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700654 raise ExternalError(
655 "Failed to unzip input target-files \"{}\":\n{}".format(
656 filename, stdoutdata))
Doug Zongker55d93282011-01-25 17:03:34 -0800657
Tao Bao1c830bf2017-12-25 10:43:47 -0800658 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800659 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
660 if m:
661 unzip_to_dir(m.group(1), tmp)
662 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
663 filename = m.group(1)
664 else:
665 unzip_to_dir(filename, tmp)
666
Tao Baodba59ee2018-01-09 13:21:02 -0800667 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700668
669
Tao Baoe709b092018-02-07 12:40:00 -0800670def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -0800671 """Returns a SparseImage object suitable for passing to BlockImageDiff.
672
673 This function loads the specified sparse image from the given path, and
674 performs additional processing for OTA purpose. For example, it always adds
675 block 0 to clobbered blocks list. It also detects files that cannot be
676 reconstructed from the block list, for whom we should avoid applying imgdiff.
677
678 Args:
679 which: The partition name, which must be "system" or "vendor".
680 tmpdir: The directory that contains the prebuilt image and block map file.
681 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800682 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -0800683
684 Returns:
685 A SparseImage object, with file_map info loaded.
686 """
687 assert which in ("system", "vendor")
688
689 path = os.path.join(tmpdir, "IMAGES", which + ".img")
690 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
691
692 # The image and map files must have been created prior to calling
693 # ota_from_target_files.py (since LMP).
694 assert os.path.exists(path) and os.path.exists(mappath)
695
696 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
697 # it to clobbered_blocks so that it will be written to the target
698 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
699 clobbered_blocks = "0"
700
Tao Baoe709b092018-02-07 12:40:00 -0800701 image = sparse_img.SparseImage(path, mappath, clobbered_blocks,
702 allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -0800703
704 # block.map may contain less blocks, because mke2fs may skip allocating blocks
705 # if they contain all zeros. We can't reconstruct such a file from its block
706 # list. Tag such entries accordingly. (Bug: 65213616)
707 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800708 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700709 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800710 continue
711
Tao Baod3554e62018-07-10 15:31:22 -0700712 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that when
713 # using system_root_image, the filename listed in system.map may contain an
714 # additional leading slash (i.e. "//system/framework/am.jar"). Using lstrip
715 # to get consistent results.
716 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
717
718 # Special handling another case with system_root_image, where files not
719 # under /system (e.g. "/sbin/charger") are packed under ROOT/ in a
720 # target_files.zip.
721 if which == 'system' and not arcname.startswith('SYSTEM'):
722 arcname = 'ROOT/' + arcname
723
724 assert arcname in input_zip.namelist(), \
725 "Failed to find the ZIP entry for {}".format(entry)
726
Tao Baoc765cca2018-01-31 17:32:40 -0800727 info = input_zip.getinfo(arcname)
728 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800729
730 # If a RangeSet has been tagged as using shared blocks while loading the
731 # image, its block list must be already incomplete due to that reason. Don't
732 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
733 if ranges.extra.get('uses_shared_blocks'):
734 continue
735
Tao Baoc765cca2018-01-31 17:32:40 -0800736 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
737 ranges.extra['incomplete'] = True
738
739 return image
740
741
Doug Zongkereef39442009-04-02 12:14:19 -0700742def GetKeyPasswords(keylist):
743 """Given a list of keys, prompt the user to enter passwords for
744 those which require them. Return a {key: password} dict. password
745 will be None if the key has no password."""
746
Doug Zongker8ce7c252009-05-22 13:34:54 -0700747 no_passwords = []
748 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700749 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700750 devnull = open("/dev/null", "w+b")
751 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800752 # We don't need a password for things that aren't really keys.
753 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700754 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700755 continue
756
T.R. Fullhart37e10522013-03-18 10:31:26 -0700757 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700758 "-inform", "DER", "-nocrypt"],
759 stdin=devnull.fileno(),
760 stdout=devnull.fileno(),
761 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700762 p.communicate()
763 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700764 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700765 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700766 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700767 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
768 "-inform", "DER", "-passin", "pass:"],
769 stdin=devnull.fileno(),
770 stdout=devnull.fileno(),
771 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700772 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700773 if p.returncode == 0:
774 # Encrypted key with empty string as password.
775 key_passwords[k] = ''
776 elif stderr.startswith('Error decrypting key'):
777 # Definitely encrypted key.
778 # It would have said "Error reading key" if it didn't parse correctly.
779 need_passwords.append(k)
780 else:
781 # Potentially, a type of key that openssl doesn't understand.
782 # We'll let the routines in signapk.jar handle it.
783 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700784 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700785
T.R. Fullhart37e10522013-03-18 10:31:26 -0700786 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800787 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700788 return key_passwords
789
790
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800791def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700792 """Gets the minSdkVersion declared in the APK.
793
794 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
795 This can be both a decimal number (API Level) or a codename.
796
797 Args:
798 apk_name: The APK filename.
799
800 Returns:
801 The parsed SDK version string.
802
803 Raises:
804 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800805 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700806 proc = Run(
807 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
808 stderr=subprocess.PIPE)
809 stdoutdata, stderrdata = proc.communicate()
810 if proc.returncode != 0:
811 raise ExternalError(
812 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
813 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800814
Tao Baof47bf0f2018-03-21 23:28:51 -0700815 for line in stdoutdata.split("\n"):
816 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800817 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
818 if m:
819 return m.group(1)
820 raise ExternalError("No minSdkVersion returned by aapt")
821
822
823def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700824 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800825
Tao Baof47bf0f2018-03-21 23:28:51 -0700826 If minSdkVersion is set to a codename, it is translated to a number using the
827 provided map.
828
829 Args:
830 apk_name: The APK filename.
831
832 Returns:
833 The parsed SDK version number.
834
835 Raises:
836 ExternalError: On failing to get the min SDK version number.
837 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800838 version = GetMinSdkVersion(apk_name)
839 try:
840 return int(version)
841 except ValueError:
842 # Not a decimal number. Codename?
843 if version in codename_to_api_level_map:
844 return codename_to_api_level_map[version]
845 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700846 raise ExternalError(
847 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
848 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800849
850
851def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800852 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700853 """Sign the input_name zip/jar/apk, producing output_name. Use the
854 given key and password (the latter may be None if the key does not
855 have a password.
856
Doug Zongker951495f2009-08-14 12:44:19 -0700857 If whole_file is true, use the "-w" option to SignApk to embed a
858 signature that covers the whole file in the archive comment of the
859 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800860
861 min_api_level is the API Level (int) of the oldest platform this file may end
862 up on. If not specified for an APK, the API Level is obtained by interpreting
863 the minSdkVersion attribute of the APK's AndroidManifest.xml.
864
865 codename_to_api_level_map is needed to translate the codename which may be
866 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700867 """
Tao Bao76def242017-11-21 09:25:31 -0800868 if codename_to_api_level_map is None:
869 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700870
Alex Klyubin9667b182015-12-10 13:38:50 -0800871 java_library_path = os.path.join(
872 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
873
Tao Baoe95540e2016-11-08 12:08:53 -0800874 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
875 ["-Djava.library.path=" + java_library_path,
876 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
877 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700878 if whole_file:
879 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800880
881 min_sdk_version = min_api_level
882 if min_sdk_version is None:
883 if not whole_file:
884 min_sdk_version = GetMinSdkVersionInt(
885 input_name, codename_to_api_level_map)
886 if min_sdk_version is not None:
887 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
888
T.R. Fullhart37e10522013-03-18 10:31:26 -0700889 cmd.extend([key + OPTIONS.public_key_suffix,
890 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800891 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700892
Tao Bao80921982018-03-21 21:02:19 -0700893 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
894 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700895 if password is not None:
896 password += "\n"
Tao Bao80921982018-03-21 21:02:19 -0700897 stdoutdata, _ = p.communicate(password)
Doug Zongkereef39442009-04-02 12:14:19 -0700898 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700899 raise ExternalError(
900 "Failed to run signapk.jar: return code {}:\n{}".format(
901 p.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700902
Doug Zongkereef39442009-04-02 12:14:19 -0700903
Doug Zongker37974732010-09-16 17:44:38 -0700904def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800905 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700906
Tao Bao9dd909e2017-11-14 11:27:32 -0800907 For non-AVB images, raise exception if the data is too big. Print a warning
908 if the data is nearing the maximum size.
909
910 For AVB images, the actual image size should be identical to the limit.
911
912 Args:
913 data: A string that contains all the data for the partition.
914 target: The partition name. The ".img" suffix is optional.
915 info_dict: The dict to be looked up for relevant info.
916 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700917 if target.endswith(".img"):
918 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700919 mount_point = "/" + target
920
Ying Wangf8824af2014-06-03 14:07:27 -0700921 fs_type = None
922 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700923 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700924 if mount_point == "/userdata":
925 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700926 p = info_dict["fstab"][mount_point]
927 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800928 device = p.device
929 if "/" in device:
930 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -0800931 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -0700932 if not fs_type or not limit:
933 return
Doug Zongkereef39442009-04-02 12:14:19 -0700934
Andrew Boie0f9aec82012-02-14 09:32:52 -0800935 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800936 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
937 # path.
938 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
939 if size != limit:
940 raise ExternalError(
941 "Mismatching image size for %s: expected %d actual %d" % (
942 target, limit, size))
943 else:
944 pct = float(size) * 100.0 / limit
945 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
946 if pct >= 99.0:
947 raise ExternalError(msg)
948 elif pct >= 95.0:
949 print("\n WARNING: %s\n" % (msg,))
950 elif OPTIONS.verbose:
951 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700952
953
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800954def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800955 """Parses the APK certs info from a given target-files zip.
956
957 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
958 tuple with the following elements: (1) a dictionary that maps packages to
959 certs (based on the "certificate" and "private_key" attributes in the file;
960 (2) a string representing the extension of compressed APKs in the target files
961 (e.g ".gz", ".bro").
962
963 Args:
964 tf_zip: The input target_files ZipFile (already open).
965
966 Returns:
967 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
968 the extension string of compressed APKs (e.g. ".gz"), or None if there's
969 no compressed APKs.
970 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800971 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100972 compressed_extension = None
973
Tao Bao0f990332017-09-08 19:02:54 -0700974 # META/apkcerts.txt contains the info for _all_ the packages known at build
975 # time. Filter out the ones that are not installed.
976 installed_files = set()
977 for name in tf_zip.namelist():
978 basename = os.path.basename(name)
979 if basename:
980 installed_files.add(basename)
981
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800982 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
983 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700984 if not line:
985 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800986 m = re.match(
987 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
988 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
989 line)
990 if not m:
991 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100992
Tao Bao818ddf52018-01-05 11:17:34 -0800993 matches = m.groupdict()
994 cert = matches["CERT"]
995 privkey = matches["PRIVKEY"]
996 name = matches["NAME"]
997 this_compressed_extension = matches["COMPRESSED"]
998
999 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1000 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1001 if cert in SPECIAL_CERT_STRINGS and not privkey:
1002 certmap[name] = cert
1003 elif (cert.endswith(OPTIONS.public_key_suffix) and
1004 privkey.endswith(OPTIONS.private_key_suffix) and
1005 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1006 certmap[name] = cert[:-public_key_suffix_len]
1007 else:
1008 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1009
1010 if not this_compressed_extension:
1011 continue
1012
1013 # Only count the installed files.
1014 filename = name + '.' + this_compressed_extension
1015 if filename not in installed_files:
1016 continue
1017
1018 # Make sure that all the values in the compression map have the same
1019 # extension. We don't support multiple compression methods in the same
1020 # system image.
1021 if compressed_extension:
1022 if this_compressed_extension != compressed_extension:
1023 raise ValueError(
1024 "Multiple compressed extensions: {} vs {}".format(
1025 compressed_extension, this_compressed_extension))
1026 else:
1027 compressed_extension = this_compressed_extension
1028
1029 return (certmap,
1030 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001031
1032
Doug Zongkereef39442009-04-02 12:14:19 -07001033COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001034Global options
1035
1036 -p (--path) <dir>
1037 Prepend <dir>/bin to the list of places to search for binaries run by this
1038 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001039
Doug Zongker05d3dea2009-06-22 11:32:31 -07001040 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001041 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001042
Tao Bao30df8b42018-04-23 15:32:53 -07001043 -x (--extra) <key=value>
1044 Add a key/value pair to the 'extras' dict, which device-specific extension
1045 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001046
Doug Zongkereef39442009-04-02 12:14:19 -07001047 -v (--verbose)
1048 Show command lines being executed.
1049
1050 -h (--help)
1051 Display this usage message and exit.
1052"""
1053
1054def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001055 print(docstring.rstrip("\n"))
1056 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001057
1058
1059def ParseOptions(argv,
1060 docstring,
1061 extra_opts="", extra_long_opts=(),
1062 extra_option_handler=None):
1063 """Parse the options in argv and return any arguments that aren't
1064 flags. docstring is the calling module's docstring, to be displayed
1065 for errors and -h. extra_opts and extra_long_opts are for flags
1066 defined by the caller, which are processed by passing them to
1067 extra_option_handler."""
1068
1069 try:
1070 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001071 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001072 ["help", "verbose", "path=", "signapk_path=",
1073 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001074 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001075 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1076 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001077 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001078 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001079 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001080 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001081 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001082 sys.exit(2)
1083
Doug Zongkereef39442009-04-02 12:14:19 -07001084 for o, a in opts:
1085 if o in ("-h", "--help"):
1086 Usage(docstring)
1087 sys.exit()
1088 elif o in ("-v", "--verbose"):
1089 OPTIONS.verbose = True
1090 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001091 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001092 elif o in ("--signapk_path",):
1093 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001094 elif o in ("--signapk_shared_library_path",):
1095 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001096 elif o in ("--extra_signapk_args",):
1097 OPTIONS.extra_signapk_args = shlex.split(a)
1098 elif o in ("--java_path",):
1099 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001100 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001101 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001102 elif o in ("--public_key_suffix",):
1103 OPTIONS.public_key_suffix = a
1104 elif o in ("--private_key_suffix",):
1105 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001106 elif o in ("--boot_signer_path",):
1107 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001108 elif o in ("--boot_signer_args",):
1109 OPTIONS.boot_signer_args = shlex.split(a)
1110 elif o in ("--verity_signer_path",):
1111 OPTIONS.verity_signer_path = a
1112 elif o in ("--verity_signer_args",):
1113 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001114 elif o in ("-s", "--device_specific"):
1115 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001116 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001117 key, value = a.split("=", 1)
1118 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001119 else:
1120 if extra_option_handler is None or not extra_option_handler(o, a):
1121 assert False, "unknown option \"%s\"" % (o,)
1122
Doug Zongker85448772014-09-09 14:59:20 -07001123 if OPTIONS.search_path:
1124 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1125 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001126
1127 return args
1128
1129
Tao Bao4c851b12016-09-19 13:54:38 -07001130def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001131 """Make a temp file and add it to the list of things to be deleted
1132 when Cleanup() is called. Return the filename."""
1133 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1134 os.close(fd)
1135 OPTIONS.tempfiles.append(fn)
1136 return fn
1137
1138
Tao Bao1c830bf2017-12-25 10:43:47 -08001139def MakeTempDir(prefix='tmp', suffix=''):
1140 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1141
1142 Returns:
1143 The absolute pathname of the new directory.
1144 """
1145 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1146 OPTIONS.tempfiles.append(dir_name)
1147 return dir_name
1148
1149
Doug Zongkereef39442009-04-02 12:14:19 -07001150def Cleanup():
1151 for i in OPTIONS.tempfiles:
1152 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001153 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001154 else:
1155 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001156 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001157
1158
1159class PasswordManager(object):
1160 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001161 self.editor = os.getenv("EDITOR")
1162 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001163
1164 def GetPasswords(self, items):
1165 """Get passwords corresponding to each string in 'items',
1166 returning a dict. (The dict may have keys in addition to the
1167 values in 'items'.)
1168
1169 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1170 user edit that file to add more needed passwords. If no editor is
1171 available, or $ANDROID_PW_FILE isn't define, prompts the user
1172 interactively in the ordinary way.
1173 """
1174
1175 current = self.ReadFile()
1176
1177 first = True
1178 while True:
1179 missing = []
1180 for i in items:
1181 if i not in current or not current[i]:
1182 missing.append(i)
1183 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001184 if not missing:
1185 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001186
1187 for i in missing:
1188 current[i] = ""
1189
1190 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001191 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001192 answer = raw_input("try to edit again? [y]> ").strip()
1193 if answer and answer[0] not in 'yY':
1194 raise RuntimeError("key passwords unavailable")
1195 first = False
1196
1197 current = self.UpdateAndReadFile(current)
1198
Dan Albert8b72aef2015-03-23 19:13:21 -07001199 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001200 """Prompt the user to enter a value (password) for each key in
1201 'current' whose value is fales. Returns a new dict with all the
1202 values.
1203 """
1204 result = {}
1205 for k, v in sorted(current.iteritems()):
1206 if v:
1207 result[k] = v
1208 else:
1209 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001210 result[k] = getpass.getpass(
1211 "Enter password for %s key> " % k).strip()
1212 if result[k]:
1213 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001214 return result
1215
1216 def UpdateAndReadFile(self, current):
1217 if not self.editor or not self.pwfile:
1218 return self.PromptResult(current)
1219
1220 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001221 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001222 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1223 f.write("# (Additional spaces are harmless.)\n\n")
1224
1225 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001226 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1227 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001228 f.write("[[[ %s ]]] %s\n" % (v, k))
1229 if not v and first_line is None:
1230 # position cursor on first line with no password.
1231 first_line = i + 4
1232 f.close()
1233
1234 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1235 _, _ = p.communicate()
1236
1237 return self.ReadFile()
1238
1239 def ReadFile(self):
1240 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001241 if self.pwfile is None:
1242 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001243 try:
1244 f = open(self.pwfile, "r")
1245 for line in f:
1246 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001247 if not line or line[0] == '#':
1248 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001249 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1250 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001251 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001252 else:
1253 result[m.group(2)] = m.group(1)
1254 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001255 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001256 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001257 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001258 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001259
1260
Dan Albert8e0178d2015-01-27 15:53:15 -08001261def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1262 compress_type=None):
1263 import datetime
1264
1265 # http://b/18015246
1266 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1267 # for files larger than 2GiB. We can work around this by adjusting their
1268 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1269 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1270 # it isn't clear to me exactly what circumstances cause this).
1271 # `zipfile.write()` must be used directly to work around this.
1272 #
1273 # This mess can be avoided if we port to python3.
1274 saved_zip64_limit = zipfile.ZIP64_LIMIT
1275 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1276
1277 if compress_type is None:
1278 compress_type = zip_file.compression
1279 if arcname is None:
1280 arcname = filename
1281
1282 saved_stat = os.stat(filename)
1283
1284 try:
1285 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1286 # file to be zipped and reset it when we're done.
1287 os.chmod(filename, perms)
1288
1289 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001290 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1291 # intentional. zip stores datetimes in local time without a time zone
1292 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1293 # in the zip archive.
1294 local_epoch = datetime.datetime.fromtimestamp(0)
1295 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001296 os.utime(filename, (timestamp, timestamp))
1297
1298 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1299 finally:
1300 os.chmod(filename, saved_stat.st_mode)
1301 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1302 zipfile.ZIP64_LIMIT = saved_zip64_limit
1303
1304
Tao Bao58c1b962015-05-20 09:32:18 -07001305def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001306 compress_type=None):
1307 """Wrap zipfile.writestr() function to work around the zip64 limit.
1308
1309 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1310 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1311 when calling crc32(bytes).
1312
1313 But it still works fine to write a shorter string into a large zip file.
1314 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1315 when we know the string won't be too long.
1316 """
1317
1318 saved_zip64_limit = zipfile.ZIP64_LIMIT
1319 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1320
1321 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1322 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001323 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001324 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001325 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001326 else:
Tao Baof3282b42015-04-01 11:21:55 -07001327 zinfo = zinfo_or_arcname
1328
1329 # If compress_type is given, it overrides the value in zinfo.
1330 if compress_type is not None:
1331 zinfo.compress_type = compress_type
1332
Tao Bao58c1b962015-05-20 09:32:18 -07001333 # If perms is given, it has a priority.
1334 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001335 # If perms doesn't set the file type, mark it as a regular file.
1336 if perms & 0o770000 == 0:
1337 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001338 zinfo.external_attr = perms << 16
1339
Tao Baof3282b42015-04-01 11:21:55 -07001340 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001341 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1342
Dan Albert8b72aef2015-03-23 19:13:21 -07001343 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001344 zipfile.ZIP64_LIMIT = saved_zip64_limit
1345
1346
Tao Bao89d7ab22017-12-14 17:05:33 -08001347def ZipDelete(zip_filename, entries):
1348 """Deletes entries from a ZIP file.
1349
1350 Since deleting entries from a ZIP file is not supported, it shells out to
1351 'zip -d'.
1352
1353 Args:
1354 zip_filename: The name of the ZIP file.
1355 entries: The name of the entry, or the list of names to be deleted.
1356
1357 Raises:
1358 AssertionError: In case of non-zero return from 'zip'.
1359 """
1360 if isinstance(entries, basestring):
1361 entries = [entries]
1362 cmd = ["zip", "-d", zip_filename] + entries
1363 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1364 stdoutdata, _ = proc.communicate()
1365 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1366 stdoutdata)
1367
1368
Tao Baof3282b42015-04-01 11:21:55 -07001369def ZipClose(zip_file):
1370 # http://b/18015246
1371 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1372 # central directory.
1373 saved_zip64_limit = zipfile.ZIP64_LIMIT
1374 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1375
1376 zip_file.close()
1377
1378 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001379
1380
1381class DeviceSpecificParams(object):
1382 module = None
1383 def __init__(self, **kwargs):
1384 """Keyword arguments to the constructor become attributes of this
1385 object, which is passed to all functions in the device-specific
1386 module."""
1387 for k, v in kwargs.iteritems():
1388 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001389 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001390
1391 if self.module is None:
1392 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001393 if not path:
1394 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001395 try:
1396 if os.path.isdir(path):
1397 info = imp.find_module("releasetools", [path])
1398 else:
1399 d, f = os.path.split(path)
1400 b, x = os.path.splitext(f)
1401 if x == ".py":
1402 f = b
1403 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001404 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001405 self.module = imp.load_module("device_specific", *info)
1406 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001407 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001408
1409 def _DoCall(self, function_name, *args, **kwargs):
1410 """Call the named function in the device-specific module, passing
1411 the given args and kwargs. The first argument to the call will be
1412 the DeviceSpecific object itself. If there is no module, or the
1413 module does not define the function, return the value of the
1414 'default' kwarg (which itself defaults to None)."""
1415 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001416 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001417 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1418
1419 def FullOTA_Assertions(self):
1420 """Called after emitting the block of assertions at the top of a
1421 full OTA package. Implementations can add whatever additional
1422 assertions they like."""
1423 return self._DoCall("FullOTA_Assertions")
1424
Doug Zongkere5ff5902012-01-17 10:55:37 -08001425 def FullOTA_InstallBegin(self):
1426 """Called at the start of full OTA installation."""
1427 return self._DoCall("FullOTA_InstallBegin")
1428
Doug Zongker05d3dea2009-06-22 11:32:31 -07001429 def FullOTA_InstallEnd(self):
1430 """Called at the end of full OTA installation; typically this is
1431 used to install the image for the device's baseband processor."""
1432 return self._DoCall("FullOTA_InstallEnd")
1433
1434 def IncrementalOTA_Assertions(self):
1435 """Called after emitting the block of assertions at the top of an
1436 incremental OTA package. Implementations can add whatever
1437 additional assertions they like."""
1438 return self._DoCall("IncrementalOTA_Assertions")
1439
Doug Zongkere5ff5902012-01-17 10:55:37 -08001440 def IncrementalOTA_VerifyBegin(self):
1441 """Called at the start of the verification phase of incremental
1442 OTA installation; additional checks can be placed here to abort
1443 the script before any changes are made."""
1444 return self._DoCall("IncrementalOTA_VerifyBegin")
1445
Doug Zongker05d3dea2009-06-22 11:32:31 -07001446 def IncrementalOTA_VerifyEnd(self):
1447 """Called at the end of the verification phase of incremental OTA
1448 installation; additional checks can be placed here to abort the
1449 script before any changes are made."""
1450 return self._DoCall("IncrementalOTA_VerifyEnd")
1451
Doug Zongkere5ff5902012-01-17 10:55:37 -08001452 def IncrementalOTA_InstallBegin(self):
1453 """Called at the start of incremental OTA installation (after
1454 verification is complete)."""
1455 return self._DoCall("IncrementalOTA_InstallBegin")
1456
Doug Zongker05d3dea2009-06-22 11:32:31 -07001457 def IncrementalOTA_InstallEnd(self):
1458 """Called at the end of incremental OTA installation; typically
1459 this is used to install the image for the device's baseband
1460 processor."""
1461 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001462
Tao Bao9bc6bb22015-11-09 16:58:28 -08001463 def VerifyOTA_Assertions(self):
1464 return self._DoCall("VerifyOTA_Assertions")
1465
Tao Bao76def242017-11-21 09:25:31 -08001466
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001467class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001468 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001469 self.name = name
1470 self.data = data
1471 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001472 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001473 self.sha1 = sha1(data).hexdigest()
1474
1475 @classmethod
1476 def FromLocalFile(cls, name, diskname):
1477 f = open(diskname, "rb")
1478 data = f.read()
1479 f.close()
1480 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001481
1482 def WriteToTemp(self):
1483 t = tempfile.NamedTemporaryFile()
1484 t.write(self.data)
1485 t.flush()
1486 return t
1487
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001488 def WriteToDir(self, d):
1489 with open(os.path.join(d, self.name), "wb") as fp:
1490 fp.write(self.data)
1491
Geremy Condra36bd3652014-02-06 19:45:10 -08001492 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001493 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001494
Tao Bao76def242017-11-21 09:25:31 -08001495
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001496DIFF_PROGRAM_BY_EXT = {
1497 ".gz" : "imgdiff",
1498 ".zip" : ["imgdiff", "-z"],
1499 ".jar" : ["imgdiff", "-z"],
1500 ".apk" : ["imgdiff", "-z"],
1501 ".img" : "imgdiff",
1502 }
1503
Tao Bao76def242017-11-21 09:25:31 -08001504
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001505class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001506 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001507 self.tf = tf
1508 self.sf = sf
1509 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001510 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001511
1512 def ComputePatch(self):
1513 """Compute the patch (as a string of data) needed to turn sf into
1514 tf. Returns the same tuple as GetPatch()."""
1515
1516 tf = self.tf
1517 sf = self.sf
1518
Doug Zongker24cd2802012-08-14 16:36:15 -07001519 if self.diff_program:
1520 diff_program = self.diff_program
1521 else:
1522 ext = os.path.splitext(tf.name)[1]
1523 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001524
1525 ttemp = tf.WriteToTemp()
1526 stemp = sf.WriteToTemp()
1527
1528 ext = os.path.splitext(tf.name)[1]
1529
1530 try:
1531 ptemp = tempfile.NamedTemporaryFile()
1532 if isinstance(diff_program, list):
1533 cmd = copy.copy(diff_program)
1534 else:
1535 cmd = [diff_program]
1536 cmd.append(stemp.name)
1537 cmd.append(ttemp.name)
1538 cmd.append(ptemp.name)
1539 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001540 err = []
1541 def run():
1542 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001543 if e:
1544 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001545 th = threading.Thread(target=run)
1546 th.start()
1547 th.join(timeout=300) # 5 mins
1548 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001549 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001550 p.terminate()
1551 th.join(5)
1552 if th.is_alive():
1553 p.kill()
1554 th.join()
1555
Tianjie Xua2a9f992018-01-05 15:15:54 -08001556 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001557 print("WARNING: failure running %s:\n%s\n" % (
1558 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001559 self.patch = None
1560 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001561 diff = ptemp.read()
1562 finally:
1563 ptemp.close()
1564 stemp.close()
1565 ttemp.close()
1566
1567 self.patch = diff
1568 return self.tf, self.sf, self.patch
1569
1570
1571 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001572 """Returns a tuple of (target_file, source_file, patch_data).
1573
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001574 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001575 computing the patch failed.
1576 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001577 return self.tf, self.sf, self.patch
1578
1579
1580def ComputeDifferences(diffs):
1581 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001582 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001583
1584 # Do the largest files first, to try and reduce the long-pole effect.
1585 by_size = [(i.tf.size, i) for i in diffs]
1586 by_size.sort(reverse=True)
1587 by_size = [i[1] for i in by_size]
1588
1589 lock = threading.Lock()
1590 diff_iter = iter(by_size) # accessed under lock
1591
1592 def worker():
1593 try:
1594 lock.acquire()
1595 for d in diff_iter:
1596 lock.release()
1597 start = time.time()
1598 d.ComputePatch()
1599 dur = time.time() - start
1600 lock.acquire()
1601
1602 tf, sf, patch = d.GetPatch()
1603 if sf.name == tf.name:
1604 name = tf.name
1605 else:
1606 name = "%s (%s)" % (tf.name, sf.name)
1607 if patch is None:
Tao Bao76def242017-11-21 09:25:31 -08001608 print(
1609 "patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001610 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001611 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1612 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001613 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001614 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001615 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001616 raise
1617
1618 # start worker threads; wait for them all to finish.
1619 threads = [threading.Thread(target=worker)
1620 for i in range(OPTIONS.worker_threads)]
1621 for th in threads:
1622 th.start()
1623 while threads:
1624 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001625
1626
Dan Albert8b72aef2015-03-23 19:13:21 -07001627class BlockDifference(object):
1628 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001629 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001630 self.tgt = tgt
1631 self.src = src
1632 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001633 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001634 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001635
Tao Baodd2a5892015-03-12 12:32:37 -07001636 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001637 version = max(
1638 int(i) for i in
1639 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001640 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001641 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001642
1643 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001644 version=self.version,
1645 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001646 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001647 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001648 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001649 self.touched_src_ranges = b.touched_src_ranges
1650 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001651
Tao Baoaac4ad52015-10-16 15:26:34 -07001652 if src is None:
1653 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1654 else:
1655 _, self.device = GetTypeAndDevice("/" + partition,
1656 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001657
Tao Baod8d14be2016-02-04 14:26:02 -08001658 @property
1659 def required_cache(self):
1660 return self._required_cache
1661
Tao Bao76def242017-11-21 09:25:31 -08001662 def WriteScript(self, script, output_zip, progress=None,
1663 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001664 if not self.src:
1665 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001666 script.Print("Patching %s image unconditionally..." % (self.partition,))
1667 else:
1668 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001669
Dan Albert8b72aef2015-03-23 19:13:21 -07001670 if progress:
1671 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001672 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001673
1674 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001675 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001676
Tao Bao9bc6bb22015-11-09 16:58:28 -08001677 def WriteStrictVerifyScript(self, script):
1678 """Verify all the blocks in the care_map, including clobbered blocks.
1679
1680 This differs from the WriteVerifyScript() function: a) it prints different
1681 error messages; b) it doesn't allow half-way updated images to pass the
1682 verification."""
1683
1684 partition = self.partition
1685 script.Print("Verifying %s..." % (partition,))
1686 ranges = self.tgt.care_map
1687 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001688 script.AppendExtra(
1689 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1690 'ui_print("\\"%s\\" has unexpected contents.");' % (
1691 self.device, ranges_str,
1692 self.tgt.TotalSha1(include_clobbered_blocks=True),
1693 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001694 script.AppendExtra("")
1695
Tao Baod522bdc2016-04-12 15:53:16 -07001696 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001697 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001698
1699 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001700 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001701 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001702
1703 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001704 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001705 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001706 ranges = self.touched_src_ranges
1707 expected_sha1 = self.touched_src_sha1
1708 else:
1709 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1710 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001711
1712 # No blocks to be checked, skipping.
1713 if not ranges:
1714 return
1715
Tao Bao5ece99d2015-05-12 11:42:31 -07001716 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001717 script.AppendExtra(
1718 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1719 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1720 '"%s.patch.dat")) then' % (
1721 self.device, ranges_str, expected_sha1,
1722 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001723 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001724 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001725
Tianjie Xufc3422a2015-12-15 11:53:59 -08001726 if self.version >= 4:
1727
1728 # Bug: 21124327
1729 # When generating incrementals for the system and vendor partitions in
1730 # version 4 or newer, explicitly check the first block (which contains
1731 # the superblock) of the partition to see if it's what we expect. If
1732 # this check fails, give an explicit log message about the partition
1733 # having been remounted R/W (the most likely explanation).
1734 if self.check_first_block:
1735 script.AppendExtra('check_first_block("%s");' % (self.device,))
1736
1737 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001738 if partition == "system":
1739 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1740 else:
1741 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001742 script.AppendExtra((
1743 'ifelse (block_image_recover("{device}", "{ranges}") && '
1744 'block_image_verify("{device}", '
1745 'package_extract_file("{partition}.transfer.list"), '
1746 '"{partition}.new.dat", "{partition}.patch.dat"), '
1747 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001748 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001749 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001750 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001751
Tao Baodd2a5892015-03-12 12:32:37 -07001752 # Abort the OTA update. Note that the incremental OTA cannot be applied
1753 # even if it may match the checksum of the target partition.
1754 # a) If version < 3, operations like move and erase will make changes
1755 # unconditionally and damage the partition.
1756 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001757 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001758 if partition == "system":
1759 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1760 else:
1761 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1762 script.AppendExtra((
1763 'abort("E%d: %s partition has unexpected contents");\n'
1764 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001765
Tao Bao5fcaaef2015-06-01 13:40:49 -07001766 def _WritePostInstallVerifyScript(self, script):
1767 partition = self.partition
1768 script.Print('Verifying the updated %s image...' % (partition,))
1769 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1770 ranges = self.tgt.care_map
1771 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001772 script.AppendExtra(
1773 'if range_sha1("%s", "%s") == "%s" then' % (
1774 self.device, ranges_str,
1775 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001776
1777 # Bug: 20881595
1778 # Verify that extended blocks are really zeroed out.
1779 if self.tgt.extended:
1780 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001781 script.AppendExtra(
1782 'if range_sha1("%s", "%s") == "%s" then' % (
1783 self.device, ranges_str,
1784 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001785 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001786 if partition == "system":
1787 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1788 else:
1789 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001790 script.AppendExtra(
1791 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001792 ' abort("E%d: %s partition has unexpected non-zero contents after '
1793 'OTA update");\n'
1794 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001795 else:
1796 script.Print('Verified the updated %s image.' % (partition,))
1797
Tianjie Xu209db462016-05-24 17:34:52 -07001798 if partition == "system":
1799 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1800 else:
1801 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1802
Tao Bao5fcaaef2015-06-01 13:40:49 -07001803 script.AppendExtra(
1804 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001805 ' abort("E%d: %s partition has unexpected contents after OTA '
1806 'update");\n'
1807 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001808
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001809 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001810 ZipWrite(output_zip,
1811 '{}.transfer.list'.format(self.path),
1812 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001813
Tao Bao76def242017-11-21 09:25:31 -08001814 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1815 # its size. Quailty 9 almost triples the compression time but doesn't
1816 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001817 # zip | brotli(quality 6) | brotli(quality 9)
1818 # compressed_size: 942M | 869M (~8% reduced) | 854M
1819 # compression_time: 75s | 265s | 719s
1820 # decompression_time: 15s | 25s | 25s
1821
1822 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001823 brotli_cmd = ['brotli', '--quality=6',
1824 '--output={}.new.dat.br'.format(self.path),
1825 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001826 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao80921982018-03-21 21:02:19 -07001827 p = Run(brotli_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1828 stdoutdata, _ = p.communicate()
1829 assert p.returncode == 0, \
1830 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1831 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001832
1833 new_data_name = '{}.new.dat.br'.format(self.partition)
1834 ZipWrite(output_zip,
1835 '{}.new.dat.br'.format(self.path),
1836 new_data_name,
1837 compress_type=zipfile.ZIP_STORED)
1838 else:
1839 new_data_name = '{}.new.dat'.format(self.partition)
1840 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1841
Dan Albert8e0178d2015-01-27 15:53:15 -08001842 ZipWrite(output_zip,
1843 '{}.patch.dat'.format(self.path),
1844 '{}.patch.dat'.format(self.partition),
1845 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001846
Tianjie Xu209db462016-05-24 17:34:52 -07001847 if self.partition == "system":
1848 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1849 else:
1850 code = ErrorCode.VENDOR_UPDATE_FAILURE
1851
Dan Albert8e0178d2015-01-27 15:53:15 -08001852 call = ('block_image_update("{device}", '
1853 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001854 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001855 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001856 device=self.device, partition=self.partition,
1857 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001858 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001859
Dan Albert8b72aef2015-03-23 19:13:21 -07001860 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001861 data = source.ReadRangeSet(ranges)
1862 ctx = sha1()
1863
1864 for p in data:
1865 ctx.update(p)
1866
1867 return ctx.hexdigest()
1868
Tao Baoe9b61912015-07-09 17:37:49 -07001869 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1870 """Return the hash value for all zero blocks."""
1871 zero_block = '\x00' * 4096
1872 ctx = sha1()
1873 for _ in range(num_blocks):
1874 ctx.update(zero_block)
1875
1876 return ctx.hexdigest()
1877
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001878
1879DataImage = blockimgdiff.DataImage
1880
Tao Bao76def242017-11-21 09:25:31 -08001881
Doug Zongker96a57e72010-09-26 14:57:41 -07001882# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001883PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001884 "ext4": "EMMC",
1885 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001886 "f2fs": "EMMC",
1887 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001888}
Doug Zongker96a57e72010-09-26 14:57:41 -07001889
Tao Bao76def242017-11-21 09:25:31 -08001890
Doug Zongker96a57e72010-09-26 14:57:41 -07001891def GetTypeAndDevice(mount_point, info):
1892 fstab = info["fstab"]
1893 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001894 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1895 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001896 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001897 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001898
1899
1900def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001901 """Parses and converts a PEM-encoded certificate into DER-encoded.
1902
1903 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1904
1905 Returns:
1906 The decoded certificate string.
1907 """
1908 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001909 save = False
1910 for line in data.split("\n"):
1911 if "--END CERTIFICATE--" in line:
1912 break
1913 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001914 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001915 if "--BEGIN CERTIFICATE--" in line:
1916 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001917 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001918 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001919
Tao Bao04e1f012018-02-04 12:13:35 -08001920
1921def ExtractPublicKey(cert):
1922 """Extracts the public key (PEM-encoded) from the given certificate file.
1923
1924 Args:
1925 cert: The certificate filename.
1926
1927 Returns:
1928 The public key string.
1929
1930 Raises:
1931 AssertionError: On non-zero return from 'openssl'.
1932 """
1933 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1934 # While openssl 1.1 writes the key into the given filename followed by '-out',
1935 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1936 # stdout instead.
1937 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1938 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1939 pubkey, stderrdata = proc.communicate()
1940 assert proc.returncode == 0, \
1941 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1942 return pubkey
1943
1944
Doug Zongker412c02f2014-02-13 10:58:24 -08001945def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1946 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001947 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001948
Tao Bao6d5d6232018-03-09 17:04:42 -08001949 Most of the space in the boot and recovery images is just the kernel, which is
1950 identical for the two, so the resulting patch should be efficient. Add it to
1951 the output zip, along with a shell script that is run from init.rc on first
1952 boot to actually do the patching and install the new recovery image.
1953
1954 Args:
1955 input_dir: The top-level input directory of the target-files.zip.
1956 output_sink: The callback function that writes the result.
1957 recovery_img: File object for the recovery image.
1958 boot_img: File objects for the boot image.
1959 info_dict: A dict returned by common.LoadInfoDict() on the input
1960 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001961 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001962 if info_dict is None:
1963 info_dict = OPTIONS.info_dict
1964
Tao Bao6d5d6232018-03-09 17:04:42 -08001965 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001966
Tao Baof2cffbd2015-07-22 12:33:18 -07001967 if full_recovery_image:
1968 output_sink("etc/recovery.img", recovery_img.data)
1969
1970 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08001971 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07001972 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08001973 # With system-root-image, boot and recovery images will have mismatching
1974 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
1975 # to handle such a case.
1976 if system_root_image:
1977 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07001978 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08001979 assert not os.path.exists(path)
1980 else:
1981 diff_program = ["imgdiff"]
1982 if os.path.exists(path):
1983 diff_program.append("-b")
1984 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07001985 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08001986 else:
1987 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07001988
1989 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1990 _, _, patch = d.ComputePatch()
1991 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001992
Dan Albertebb19aa2015-03-27 19:11:53 -07001993 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001994 # The following GetTypeAndDevice()s need to use the path in the target
1995 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001996 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1997 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1998 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001999 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002000
Tao Baof2cffbd2015-07-22 12:33:18 -07002001 if full_recovery_image:
2002 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002003if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2004 applypatch \\
2005 --flash /system/etc/recovery.img \\
2006 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2007 log -t recovery "Installing new recovery image: succeeded" || \\
2008 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002009else
2010 log -t recovery "Recovery image already installed"
2011fi
2012""" % {'type': recovery_type,
2013 'device': recovery_device,
2014 'sha1': recovery_img.sha1,
2015 'size': recovery_img.size}
2016 else:
2017 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002018if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2019 applypatch %(bonus_args)s \\
2020 --patch /system/recovery-from-boot.p \\
2021 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2022 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2023 log -t recovery "Installing new recovery image: succeeded" || \\
2024 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002025else
2026 log -t recovery "Recovery image already installed"
2027fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002028""" % {'boot_size': boot_img.size,
2029 'boot_sha1': boot_img.sha1,
2030 'recovery_size': recovery_img.size,
2031 'recovery_sha1': recovery_img.sha1,
2032 'boot_type': boot_type,
2033 'boot_device': boot_device,
2034 'recovery_type': recovery_type,
2035 'recovery_device': recovery_device,
2036 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002037
2038 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002039 # in the L release.
2040 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002041
Tao Bao89fbb0f2017-01-10 10:47:58 -08002042 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002043
2044 output_sink(sh_location, sh)