blob: ee2c6f499809702a18deacf8c592062cd8be99b7 [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 Bao410ad8b2018-08-24 12:08:38 -0700152def LoadInfoDict(input_file, repacking=False):
153 """Loads the key/value pairs from the given input target_files.
154
155 It reads `META/misc_info.txt` file in the target_files input, does sanity
156 checks and returns the parsed key/value pairs for to the given build. It's
157 usually called early when working on input target_files files, e.g. when
158 generating OTAs, or signing builds. Note that the function may be called
159 against an old target_files file (i.e. from past dessert releases). So the
160 property parsing needs to be backward compatible.
161
162 In a `META/misc_info.txt`, a few properties are stored as links to the files
163 in the PRODUCT_OUT directory. It works fine with the build system. However,
164 they are no longer available when (re)generating images from target_files zip.
165 When `repacking` is True, redirect these properties to the actual files in the
166 unzipped directory.
167
168 Args:
169 input_file: The input target_files file, which could be an open
170 zipfile.ZipFile instance, or a str for the dir that contains the files
171 unzipped from a target_files file.
172 repacking: Whether it's trying repack an target_files file after loading the
173 info dict (default: False). If so, it will rewrite a few loaded
174 properties (e.g. selinux_fc, root_dir) to point to the actual files in
175 target_files file. When doing repacking, `input_file` must be a dir.
176
177 Returns:
178 A dict that contains the parsed key/value pairs.
179
180 Raises:
181 AssertionError: On invalid input arguments.
182 ValueError: On malformed input values.
183 """
184 if repacking:
185 assert isinstance(input_file, str), \
186 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700187
Doug Zongkerc9253822014-02-04 12:17:58 -0800188 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700189 if isinstance(input_file, zipfile.ZipFile):
190 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800191 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700192 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800193 try:
194 with open(path) as f:
195 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700196 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800197 if e.errno == errno.ENOENT:
198 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800199
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700200 try:
Michael Runge6e836112014-04-15 17:40:21 -0700201 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700202 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700203 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700204
Tao Bao410ad8b2018-08-24 12:08:38 -0700205 if "recovery_api_version" not in d:
206 raise ValueError("Failed to find 'recovery_api_version'")
207 if "fstab_version" not in d:
208 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800209
Tao Bao410ad8b2018-08-24 12:08:38 -0700210 if repacking:
211 # We carry a copy of file_contexts.bin under META/. If not available, search
212 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
213 # images than the one running on device, in that case, we must have the one
214 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700215 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700216 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700217 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700218
Tom Cherryd14b8952018-08-09 14:26:00 -0700219 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700220
Tom Cherryd14b8952018-08-09 14:26:00 -0700221 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700222 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700223 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700224 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700225
Tao Baof54216f2016-03-29 15:12:37 -0700226 # Redirect {system,vendor}_base_fs_file.
227 if "system_base_fs_file" in d:
228 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700229 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700230 if os.path.exists(system_base_fs_file):
231 d["system_base_fs_file"] = system_base_fs_file
232 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800233 print("Warning: failed to find system base fs file: %s" % (
234 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700235 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700236
237 if "vendor_base_fs_file" in d:
238 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700239 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700240 if os.path.exists(vendor_base_fs_file):
241 d["vendor_base_fs_file"] = vendor_base_fs_file
242 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800243 print("Warning: failed to find vendor base fs file: %s" % (
244 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700245 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700246
Doug Zongker37974732010-09-16 17:44:38 -0700247 def makeint(key):
248 if key in d:
249 d[key] = int(d[key], 0)
250
251 makeint("recovery_api_version")
252 makeint("blocksize")
253 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700254 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700255 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700256 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700257 makeint("recovery_size")
258 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800259 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700260
Tao Baoa57ab9f2018-08-24 12:08:38 -0700261 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
262 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
263 # cases, since it may load the info_dict from an old build (e.g. when
264 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800265 system_root_image = d.get("system_root_image") == "true"
266 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700267 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700268 if isinstance(input_file, zipfile.ZipFile):
269 if recovery_fstab_path not in input_file.namelist():
270 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
271 else:
272 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
273 if not os.path.exists(path):
274 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800275 d["fstab"] = LoadRecoveryFSTab(
276 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700277
Tao Bao76def242017-11-21 09:25:31 -0800278 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700279 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700280 if isinstance(input_file, zipfile.ZipFile):
281 if recovery_fstab_path not in input_file.namelist():
282 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
283 else:
284 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
285 if not os.path.exists(path):
286 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800287 d["fstab"] = LoadRecoveryFSTab(
288 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700289
Tianjie Xucfa86222016-03-07 16:31:19 -0800290 else:
291 d["fstab"] = None
292
Tao Baobcd1d162017-08-26 13:10:26 -0700293 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
294 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Tao Bao12d87fc2018-01-31 12:18:52 -0800295
296 # Set up the salt (based on fingerprint or thumbprint) that will be used when
297 # adding AVB footer.
298 if d.get("avb_enable") == "true":
299 fp = None
300 if "build.prop" in d:
301 build_prop = d["build.prop"]
302 if "ro.build.fingerprint" in build_prop:
303 fp = build_prop["ro.build.fingerprint"]
304 elif "ro.build.thumbprint" in build_prop:
305 fp = build_prop["ro.build.thumbprint"]
306 if fp:
307 d["avb_salt"] = sha256(fp).hexdigest()
308
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700309 return d
310
Tao Baod1de6f32017-03-01 16:38:48 -0800311
Tao Baobcd1d162017-08-26 13:10:26 -0700312def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700313 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700314 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700315 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700316 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700317 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700318 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700319
Tao Baod1de6f32017-03-01 16:38:48 -0800320
Michael Runge6e836112014-04-15 17:40:21 -0700321def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700322 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700323 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700324 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700325 if not line or line.startswith("#"):
326 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700327 if "=" in line:
328 name, value = line.split("=", 1)
329 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700330 return d
331
Tao Baod1de6f32017-03-01 16:38:48 -0800332
Tianjie Xucfa86222016-03-07 16:31:19 -0800333def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
334 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700335 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800336 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700337 self.mount_point = mount_point
338 self.fs_type = fs_type
339 self.device = device
340 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700341 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700342
343 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800344 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700345 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800346 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700347 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700348
Tao Baod1de6f32017-03-01 16:38:48 -0800349 assert fstab_version == 2
350
351 d = {}
352 for line in data.split("\n"):
353 line = line.strip()
354 if not line or line.startswith("#"):
355 continue
356
357 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
358 pieces = line.split()
359 if len(pieces) != 5:
360 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
361
362 # Ignore entries that are managed by vold.
363 options = pieces[4]
364 if "voldmanaged=" in options:
365 continue
366
367 # It's a good line, parse it.
368 length = 0
369 options = options.split(",")
370 for i in options:
371 if i.startswith("length="):
372 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800373 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800374 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700375 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800376
Tao Baod1de6f32017-03-01 16:38:48 -0800377 mount_flags = pieces[3]
378 # Honor the SELinux context if present.
379 context = None
380 for i in mount_flags.split(","):
381 if i.startswith("context="):
382 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800383
Tao Baod1de6f32017-03-01 16:38:48 -0800384 mount_point = pieces[1]
385 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
386 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800387
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700388 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700389 # system. Other areas assume system is always at "/system" so point /system
390 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700391 if system_root_image:
392 assert not d.has_key("/system") and d.has_key("/")
393 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700394 return d
395
396
Doug Zongker37974732010-09-16 17:44:38 -0700397def DumpInfoDict(d):
398 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800399 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700400
Dan Albert8b72aef2015-03-23 19:13:21 -0700401
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800402def AppendAVBSigningArgs(cmd, partition):
403 """Append signing arguments for avbtool."""
404 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
405 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
406 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
407 if key_path and algorithm:
408 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700409 avb_salt = OPTIONS.info_dict.get("avb_salt")
410 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700411 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700412 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800413
414
Tao Bao02a08592018-07-22 12:40:45 -0700415def GetAvbChainedPartitionArg(partition, info_dict, key=None):
416 """Constructs and returns the arg to build or verify a chained partition.
417
418 Args:
419 partition: The partition name.
420 info_dict: The info dict to look up the key info and rollback index
421 location.
422 key: The key to be used for building or verifying the partition. Defaults to
423 the key listed in info_dict.
424
425 Returns:
426 A string of form "partition:rollback_index_location:key" that can be used to
427 build or verify vbmeta image.
428
429 Raises:
430 AssertionError: When it fails to extract the public key with avbtool.
431 """
432 if key is None:
433 key = info_dict["avb_" + partition + "_key_path"]
434 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
435 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
436 proc = Run(
437 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path],
438 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
439 stdoutdata, _ = proc.communicate()
440 assert proc.returncode == 0, \
441 "Failed to extract pubkey for {}:\n{}".format(
442 partition, stdoutdata)
443
444 rollback_index_location = info_dict[
445 "avb_" + partition + "_rollback_index_location"]
446 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
447
448
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700449def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800450 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700451 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700452
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700453 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800454 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
455 we are building a two-step special image (i.e. building a recovery image to
456 be loaded into /boot in two-step OTAs).
457
458 Return the image data, or None if sourcedir does not appear to contains files
459 for building the requested image.
460 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700461
462 def make_ramdisk():
463 ramdisk_img = tempfile.NamedTemporaryFile()
464
465 if os.access(fs_config_file, os.F_OK):
466 cmd = ["mkbootfs", "-f", fs_config_file,
467 os.path.join(sourcedir, "RAMDISK")]
468 else:
469 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
470 p1 = Run(cmd, stdout=subprocess.PIPE)
471 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
472
473 p2.wait()
474 p1.wait()
475 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
476 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
477
478 return ramdisk_img
479
480 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
481 return None
482
483 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700484 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700485
Doug Zongkerd5131602012-08-02 14:46:42 -0700486 if info_dict is None:
487 info_dict = OPTIONS.info_dict
488
Doug Zongkereef39442009-04-02 12:14:19 -0700489 img = tempfile.NamedTemporaryFile()
490
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700491 if has_ramdisk:
492 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700493
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800494 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
495 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
496
497 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700498
Benoit Fradina45a8682014-07-14 21:00:43 +0200499 fn = os.path.join(sourcedir, "second")
500 if os.access(fn, os.F_OK):
501 cmd.append("--second")
502 cmd.append(fn)
503
Doug Zongker171f1cd2009-06-15 22:36:37 -0700504 fn = os.path.join(sourcedir, "cmdline")
505 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700506 cmd.append("--cmdline")
507 cmd.append(open(fn).read().rstrip("\n"))
508
509 fn = os.path.join(sourcedir, "base")
510 if os.access(fn, os.F_OK):
511 cmd.append("--base")
512 cmd.append(open(fn).read().rstrip("\n"))
513
Ying Wang4de6b5b2010-08-25 14:29:34 -0700514 fn = os.path.join(sourcedir, "pagesize")
515 if os.access(fn, os.F_OK):
516 cmd.append("--pagesize")
517 cmd.append(open(fn).read().rstrip("\n"))
518
Tao Bao76def242017-11-21 09:25:31 -0800519 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700520 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700521 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700522
Tao Bao76def242017-11-21 09:25:31 -0800523 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000524 if args and args.strip():
525 cmd.extend(shlex.split(args))
526
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700527 if has_ramdisk:
528 cmd.extend(["--ramdisk", ramdisk_img.name])
529
Tao Baod95e9fd2015-03-29 23:07:41 -0700530 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800531 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700532 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700533 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700534 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700535 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700536
Tao Baobf70c3182017-07-11 17:27:55 -0700537 # "boot" or "recovery", without extension.
538 partition_name = os.path.basename(sourcedir).lower()
539
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700540 if (partition_name == "recovery" and
541 info_dict.get("include_recovery_dtbo") == "true"):
542 fn = os.path.join(sourcedir, "recovery_dtbo")
543 cmd.extend(["--recovery_dtbo", fn])
544
Doug Zongker38a649f2009-06-17 09:07:09 -0700545 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700546 p.communicate()
Tao Baobf70c3182017-07-11 17:27:55 -0700547 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700548
Tao Bao76def242017-11-21 09:25:31 -0800549 if (info_dict.get("boot_signer") == "true" and
550 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800551 # Hard-code the path as "/boot" for two-step special recovery image (which
552 # will be loaded into /boot during the two-step OTA).
553 if two_step_image:
554 path = "/boot"
555 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700556 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700557 cmd = [OPTIONS.boot_signer_path]
558 cmd.extend(OPTIONS.boot_signer_args)
559 cmd.extend([path, img.name,
560 info_dict["verity_key"] + ".pk8",
561 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700562 p = Run(cmd, stdout=subprocess.PIPE)
563 p.communicate()
564 assert p.returncode == 0, "boot_signer of %s image failed" % path
565
Tao Baod95e9fd2015-03-29 23:07:41 -0700566 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800567 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700568 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700569 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800570 # We have switched from the prebuilt futility binary to using the tool
571 # (futility-host) built from the source. Override the setting in the old
572 # TF.zip.
573 futility = info_dict["futility"]
574 if futility.startswith("prebuilts/"):
575 futility = "futility-host"
576 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700577 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700578 info_dict["vboot_key"] + ".vbprivk",
579 info_dict["vboot_subkey"] + ".vbprivk",
580 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700581 img.name]
582 p = Run(cmd, stdout=subprocess.PIPE)
583 p.communicate()
584 assert p.returncode == 0, "vboot_signer of %s image failed" % path
585
Tao Baof3282b42015-04-01 11:21:55 -0700586 # Clean up the temp files.
587 img_unsigned.close()
588 img_keyblock.close()
589
David Zeuthen8fecb282017-12-01 16:24:01 -0500590 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800591 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700592 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500593 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400594 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700595 "--partition_size", str(part_size), "--partition_name",
596 partition_name]
597 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500598 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400599 if args and args.strip():
600 cmd.extend(shlex.split(args))
601 p = Run(cmd, stdout=subprocess.PIPE)
602 p.communicate()
603 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c3182017-07-11 17:27:55 -0700604 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500605
606 img.seek(os.SEEK_SET, 0)
607 data = img.read()
608
609 if has_ramdisk:
610 ramdisk_img.close()
611 img.close()
612
613 return data
614
615
Doug Zongkerd5131602012-08-02 14:46:42 -0700616def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800617 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700618 """Return a File object with the desired bootable image.
619
620 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
621 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
622 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700623
Doug Zongker55d93282011-01-25 17:03:34 -0800624 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
625 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800626 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800627 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700628
629 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
630 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800631 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700632 return File.FromLocalFile(name, prebuilt_path)
633
Tao Bao89fbb0f2017-01-10 10:47:58 -0800634 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700635
636 if info_dict is None:
637 info_dict = OPTIONS.info_dict
638
639 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800640 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
641 # for recovery.
642 has_ramdisk = (info_dict.get("system_root_image") != "true" or
643 prebuilt_name != "boot.img" or
644 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700645
Doug Zongker6f1d0312014-08-22 08:07:12 -0700646 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400647 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
648 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800649 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700650 if data:
651 return File(name, data)
652 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800653
Doug Zongkereef39442009-04-02 12:14:19 -0700654
Narayan Kamatha07bf042017-08-14 14:49:21 +0100655def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800656 """Gunzips the given gzip compressed file to a given output file."""
657 with gzip.open(in_filename, "rb") as in_file, \
658 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100659 shutil.copyfileobj(in_file, out_file)
660
661
Doug Zongker75f17362009-12-08 13:46:44 -0800662def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800663 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800664
Tao Bao1c830bf2017-12-25 10:43:47 -0800665 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
666 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800667
Tao Bao1c830bf2017-12-25 10:43:47 -0800668 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800669 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800670 """
Doug Zongkereef39442009-04-02 12:14:19 -0700671
Doug Zongker55d93282011-01-25 17:03:34 -0800672 def unzip_to_dir(filename, dirname):
673 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
674 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800675 cmd.extend(pattern)
Tao Bao80921982018-03-21 21:02:19 -0700676 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
677 stdoutdata, _ = p.communicate()
Doug Zongker55d93282011-01-25 17:03:34 -0800678 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700679 raise ExternalError(
680 "Failed to unzip input target-files \"{}\":\n{}".format(
681 filename, stdoutdata))
Doug Zongker55d93282011-01-25 17:03:34 -0800682
Tao Bao1c830bf2017-12-25 10:43:47 -0800683 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800684 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
685 if m:
686 unzip_to_dir(m.group(1), tmp)
687 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
688 filename = m.group(1)
689 else:
690 unzip_to_dir(filename, tmp)
691
Tao Baodba59ee2018-01-09 13:21:02 -0800692 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700693
694
Tao Baoe709b092018-02-07 12:40:00 -0800695def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -0800696 """Returns a SparseImage object suitable for passing to BlockImageDiff.
697
698 This function loads the specified sparse image from the given path, and
699 performs additional processing for OTA purpose. For example, it always adds
700 block 0 to clobbered blocks list. It also detects files that cannot be
701 reconstructed from the block list, for whom we should avoid applying imgdiff.
702
703 Args:
704 which: The partition name, which must be "system" or "vendor".
705 tmpdir: The directory that contains the prebuilt image and block map file.
706 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800707 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -0800708
709 Returns:
710 A SparseImage object, with file_map info loaded.
711 """
712 assert which in ("system", "vendor")
713
714 path = os.path.join(tmpdir, "IMAGES", which + ".img")
715 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
716
717 # The image and map files must have been created prior to calling
718 # ota_from_target_files.py (since LMP).
719 assert os.path.exists(path) and os.path.exists(mappath)
720
721 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
722 # it to clobbered_blocks so that it will be written to the target
723 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
724 clobbered_blocks = "0"
725
Tao Baoe709b092018-02-07 12:40:00 -0800726 image = sparse_img.SparseImage(path, mappath, clobbered_blocks,
727 allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -0800728
729 # block.map may contain less blocks, because mke2fs may skip allocating blocks
730 # if they contain all zeros. We can't reconstruct such a file from its block
731 # list. Tag such entries accordingly. (Bug: 65213616)
732 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800733 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700734 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800735 continue
736
Tom Cherryd14b8952018-08-09 14:26:00 -0700737 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
738 # filename listed in system.map may contain an additional leading slash
739 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
740 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700741 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
742
Tom Cherryd14b8952018-08-09 14:26:00 -0700743 # Special handling another case, where files not under /system
744 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700745 if which == 'system' and not arcname.startswith('SYSTEM'):
746 arcname = 'ROOT/' + arcname
747
748 assert arcname in input_zip.namelist(), \
749 "Failed to find the ZIP entry for {}".format(entry)
750
Tao Baoc765cca2018-01-31 17:32:40 -0800751 info = input_zip.getinfo(arcname)
752 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800753
754 # If a RangeSet has been tagged as using shared blocks while loading the
755 # image, its block list must be already incomplete due to that reason. Don't
756 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
757 if ranges.extra.get('uses_shared_blocks'):
758 continue
759
Tao Baoc765cca2018-01-31 17:32:40 -0800760 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
761 ranges.extra['incomplete'] = True
762
763 return image
764
765
Doug Zongkereef39442009-04-02 12:14:19 -0700766def GetKeyPasswords(keylist):
767 """Given a list of keys, prompt the user to enter passwords for
768 those which require them. Return a {key: password} dict. password
769 will be None if the key has no password."""
770
Doug Zongker8ce7c252009-05-22 13:34:54 -0700771 no_passwords = []
772 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700773 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700774 devnull = open("/dev/null", "w+b")
775 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800776 # We don't need a password for things that aren't really keys.
777 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700778 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700779 continue
780
T.R. Fullhart37e10522013-03-18 10:31:26 -0700781 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700782 "-inform", "DER", "-nocrypt"],
783 stdin=devnull.fileno(),
784 stdout=devnull.fileno(),
785 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700786 p.communicate()
787 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700788 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700789 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700790 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700791 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
792 "-inform", "DER", "-passin", "pass:"],
793 stdin=devnull.fileno(),
794 stdout=devnull.fileno(),
795 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700796 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700797 if p.returncode == 0:
798 # Encrypted key with empty string as password.
799 key_passwords[k] = ''
800 elif stderr.startswith('Error decrypting key'):
801 # Definitely encrypted key.
802 # It would have said "Error reading key" if it didn't parse correctly.
803 need_passwords.append(k)
804 else:
805 # Potentially, a type of key that openssl doesn't understand.
806 # We'll let the routines in signapk.jar handle it.
807 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700808 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700809
T.R. Fullhart37e10522013-03-18 10:31:26 -0700810 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800811 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700812 return key_passwords
813
814
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800815def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700816 """Gets the minSdkVersion declared in the APK.
817
818 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
819 This can be both a decimal number (API Level) or a codename.
820
821 Args:
822 apk_name: The APK filename.
823
824 Returns:
825 The parsed SDK version string.
826
827 Raises:
828 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800829 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700830 proc = Run(
831 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
832 stderr=subprocess.PIPE)
833 stdoutdata, stderrdata = proc.communicate()
834 if proc.returncode != 0:
835 raise ExternalError(
836 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
837 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800838
Tao Baof47bf0f2018-03-21 23:28:51 -0700839 for line in stdoutdata.split("\n"):
840 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800841 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
842 if m:
843 return m.group(1)
844 raise ExternalError("No minSdkVersion returned by aapt")
845
846
847def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700848 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800849
Tao Baof47bf0f2018-03-21 23:28:51 -0700850 If minSdkVersion is set to a codename, it is translated to a number using the
851 provided map.
852
853 Args:
854 apk_name: The APK filename.
855
856 Returns:
857 The parsed SDK version number.
858
859 Raises:
860 ExternalError: On failing to get the min SDK version number.
861 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800862 version = GetMinSdkVersion(apk_name)
863 try:
864 return int(version)
865 except ValueError:
866 # Not a decimal number. Codename?
867 if version in codename_to_api_level_map:
868 return codename_to_api_level_map[version]
869 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700870 raise ExternalError(
871 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
872 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800873
874
875def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800876 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700877 """Sign the input_name zip/jar/apk, producing output_name. Use the
878 given key and password (the latter may be None if the key does not
879 have a password.
880
Doug Zongker951495f2009-08-14 12:44:19 -0700881 If whole_file is true, use the "-w" option to SignApk to embed a
882 signature that covers the whole file in the archive comment of the
883 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800884
885 min_api_level is the API Level (int) of the oldest platform this file may end
886 up on. If not specified for an APK, the API Level is obtained by interpreting
887 the minSdkVersion attribute of the APK's AndroidManifest.xml.
888
889 codename_to_api_level_map is needed to translate the codename which may be
890 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700891 """
Tao Bao76def242017-11-21 09:25:31 -0800892 if codename_to_api_level_map is None:
893 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700894
Alex Klyubin9667b182015-12-10 13:38:50 -0800895 java_library_path = os.path.join(
896 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
897
Tao Baoe95540e2016-11-08 12:08:53 -0800898 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
899 ["-Djava.library.path=" + java_library_path,
900 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
901 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700902 if whole_file:
903 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800904
905 min_sdk_version = min_api_level
906 if min_sdk_version is None:
907 if not whole_file:
908 min_sdk_version = GetMinSdkVersionInt(
909 input_name, codename_to_api_level_map)
910 if min_sdk_version is not None:
911 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
912
T.R. Fullhart37e10522013-03-18 10:31:26 -0700913 cmd.extend([key + OPTIONS.public_key_suffix,
914 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800915 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700916
Tao Bao80921982018-03-21 21:02:19 -0700917 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
918 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700919 if password is not None:
920 password += "\n"
Tao Bao80921982018-03-21 21:02:19 -0700921 stdoutdata, _ = p.communicate(password)
Doug Zongkereef39442009-04-02 12:14:19 -0700922 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700923 raise ExternalError(
924 "Failed to run signapk.jar: return code {}:\n{}".format(
925 p.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700926
Doug Zongkereef39442009-04-02 12:14:19 -0700927
Doug Zongker37974732010-09-16 17:44:38 -0700928def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800929 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700930
Tao Bao9dd909e2017-11-14 11:27:32 -0800931 For non-AVB images, raise exception if the data is too big. Print a warning
932 if the data is nearing the maximum size.
933
934 For AVB images, the actual image size should be identical to the limit.
935
936 Args:
937 data: A string that contains all the data for the partition.
938 target: The partition name. The ".img" suffix is optional.
939 info_dict: The dict to be looked up for relevant info.
940 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700941 if target.endswith(".img"):
942 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700943 mount_point = "/" + target
944
Ying Wangf8824af2014-06-03 14:07:27 -0700945 fs_type = None
946 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700947 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700948 if mount_point == "/userdata":
949 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700950 p = info_dict["fstab"][mount_point]
951 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800952 device = p.device
953 if "/" in device:
954 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -0800955 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -0700956 if not fs_type or not limit:
957 return
Doug Zongkereef39442009-04-02 12:14:19 -0700958
Andrew Boie0f9aec82012-02-14 09:32:52 -0800959 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800960 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
961 # path.
962 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
963 if size != limit:
964 raise ExternalError(
965 "Mismatching image size for %s: expected %d actual %d" % (
966 target, limit, size))
967 else:
968 pct = float(size) * 100.0 / limit
969 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
970 if pct >= 99.0:
971 raise ExternalError(msg)
972 elif pct >= 95.0:
973 print("\n WARNING: %s\n" % (msg,))
974 elif OPTIONS.verbose:
975 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700976
977
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800978def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800979 """Parses the APK certs info from a given target-files zip.
980
981 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
982 tuple with the following elements: (1) a dictionary that maps packages to
983 certs (based on the "certificate" and "private_key" attributes in the file;
984 (2) a string representing the extension of compressed APKs in the target files
985 (e.g ".gz", ".bro").
986
987 Args:
988 tf_zip: The input target_files ZipFile (already open).
989
990 Returns:
991 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
992 the extension string of compressed APKs (e.g. ".gz"), or None if there's
993 no compressed APKs.
994 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800995 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100996 compressed_extension = None
997
Tao Bao0f990332017-09-08 19:02:54 -0700998 # META/apkcerts.txt contains the info for _all_ the packages known at build
999 # time. Filter out the ones that are not installed.
1000 installed_files = set()
1001 for name in tf_zip.namelist():
1002 basename = os.path.basename(name)
1003 if basename:
1004 installed_files.add(basename)
1005
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001006 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1007 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001008 if not line:
1009 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001010 m = re.match(
1011 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1012 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1013 line)
1014 if not m:
1015 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001016
Tao Bao818ddf52018-01-05 11:17:34 -08001017 matches = m.groupdict()
1018 cert = matches["CERT"]
1019 privkey = matches["PRIVKEY"]
1020 name = matches["NAME"]
1021 this_compressed_extension = matches["COMPRESSED"]
1022
1023 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1024 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1025 if cert in SPECIAL_CERT_STRINGS and not privkey:
1026 certmap[name] = cert
1027 elif (cert.endswith(OPTIONS.public_key_suffix) and
1028 privkey.endswith(OPTIONS.private_key_suffix) and
1029 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1030 certmap[name] = cert[:-public_key_suffix_len]
1031 else:
1032 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1033
1034 if not this_compressed_extension:
1035 continue
1036
1037 # Only count the installed files.
1038 filename = name + '.' + this_compressed_extension
1039 if filename not in installed_files:
1040 continue
1041
1042 # Make sure that all the values in the compression map have the same
1043 # extension. We don't support multiple compression methods in the same
1044 # system image.
1045 if compressed_extension:
1046 if this_compressed_extension != compressed_extension:
1047 raise ValueError(
1048 "Multiple compressed extensions: {} vs {}".format(
1049 compressed_extension, this_compressed_extension))
1050 else:
1051 compressed_extension = this_compressed_extension
1052
1053 return (certmap,
1054 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001055
1056
Doug Zongkereef39442009-04-02 12:14:19 -07001057COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001058Global options
1059
1060 -p (--path) <dir>
1061 Prepend <dir>/bin to the list of places to search for binaries run by this
1062 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001063
Doug Zongker05d3dea2009-06-22 11:32:31 -07001064 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001065 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001066
Tao Bao30df8b42018-04-23 15:32:53 -07001067 -x (--extra) <key=value>
1068 Add a key/value pair to the 'extras' dict, which device-specific extension
1069 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001070
Doug Zongkereef39442009-04-02 12:14:19 -07001071 -v (--verbose)
1072 Show command lines being executed.
1073
1074 -h (--help)
1075 Display this usage message and exit.
1076"""
1077
1078def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001079 print(docstring.rstrip("\n"))
1080 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001081
1082
1083def ParseOptions(argv,
1084 docstring,
1085 extra_opts="", extra_long_opts=(),
1086 extra_option_handler=None):
1087 """Parse the options in argv and return any arguments that aren't
1088 flags. docstring is the calling module's docstring, to be displayed
1089 for errors and -h. extra_opts and extra_long_opts are for flags
1090 defined by the caller, which are processed by passing them to
1091 extra_option_handler."""
1092
1093 try:
1094 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001095 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001096 ["help", "verbose", "path=", "signapk_path=",
1097 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001098 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001099 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1100 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001101 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001102 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001103 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001104 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001105 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001106 sys.exit(2)
1107
Doug Zongkereef39442009-04-02 12:14:19 -07001108 for o, a in opts:
1109 if o in ("-h", "--help"):
1110 Usage(docstring)
1111 sys.exit()
1112 elif o in ("-v", "--verbose"):
1113 OPTIONS.verbose = True
1114 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001115 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001116 elif o in ("--signapk_path",):
1117 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001118 elif o in ("--signapk_shared_library_path",):
1119 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001120 elif o in ("--extra_signapk_args",):
1121 OPTIONS.extra_signapk_args = shlex.split(a)
1122 elif o in ("--java_path",):
1123 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001124 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001125 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001126 elif o in ("--public_key_suffix",):
1127 OPTIONS.public_key_suffix = a
1128 elif o in ("--private_key_suffix",):
1129 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001130 elif o in ("--boot_signer_path",):
1131 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001132 elif o in ("--boot_signer_args",):
1133 OPTIONS.boot_signer_args = shlex.split(a)
1134 elif o in ("--verity_signer_path",):
1135 OPTIONS.verity_signer_path = a
1136 elif o in ("--verity_signer_args",):
1137 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001138 elif o in ("-s", "--device_specific"):
1139 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001140 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001141 key, value = a.split("=", 1)
1142 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001143 else:
1144 if extra_option_handler is None or not extra_option_handler(o, a):
1145 assert False, "unknown option \"%s\"" % (o,)
1146
Doug Zongker85448772014-09-09 14:59:20 -07001147 if OPTIONS.search_path:
1148 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1149 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001150
1151 return args
1152
1153
Tao Bao4c851b12016-09-19 13:54:38 -07001154def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001155 """Make a temp file and add it to the list of things to be deleted
1156 when Cleanup() is called. Return the filename."""
1157 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1158 os.close(fd)
1159 OPTIONS.tempfiles.append(fn)
1160 return fn
1161
1162
Tao Bao1c830bf2017-12-25 10:43:47 -08001163def MakeTempDir(prefix='tmp', suffix=''):
1164 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1165
1166 Returns:
1167 The absolute pathname of the new directory.
1168 """
1169 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1170 OPTIONS.tempfiles.append(dir_name)
1171 return dir_name
1172
1173
Doug Zongkereef39442009-04-02 12:14:19 -07001174def Cleanup():
1175 for i in OPTIONS.tempfiles:
1176 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001177 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001178 else:
1179 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001180 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001181
1182
1183class PasswordManager(object):
1184 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001185 self.editor = os.getenv("EDITOR")
1186 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001187
1188 def GetPasswords(self, items):
1189 """Get passwords corresponding to each string in 'items',
1190 returning a dict. (The dict may have keys in addition to the
1191 values in 'items'.)
1192
1193 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1194 user edit that file to add more needed passwords. If no editor is
1195 available, or $ANDROID_PW_FILE isn't define, prompts the user
1196 interactively in the ordinary way.
1197 """
1198
1199 current = self.ReadFile()
1200
1201 first = True
1202 while True:
1203 missing = []
1204 for i in items:
1205 if i not in current or not current[i]:
1206 missing.append(i)
1207 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001208 if not missing:
1209 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001210
1211 for i in missing:
1212 current[i] = ""
1213
1214 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001215 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001216 answer = raw_input("try to edit again? [y]> ").strip()
1217 if answer and answer[0] not in 'yY':
1218 raise RuntimeError("key passwords unavailable")
1219 first = False
1220
1221 current = self.UpdateAndReadFile(current)
1222
Dan Albert8b72aef2015-03-23 19:13:21 -07001223 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001224 """Prompt the user to enter a value (password) for each key in
1225 'current' whose value is fales. Returns a new dict with all the
1226 values.
1227 """
1228 result = {}
1229 for k, v in sorted(current.iteritems()):
1230 if v:
1231 result[k] = v
1232 else:
1233 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001234 result[k] = getpass.getpass(
1235 "Enter password for %s key> " % k).strip()
1236 if result[k]:
1237 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001238 return result
1239
1240 def UpdateAndReadFile(self, current):
1241 if not self.editor or not self.pwfile:
1242 return self.PromptResult(current)
1243
1244 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001245 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001246 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1247 f.write("# (Additional spaces are harmless.)\n\n")
1248
1249 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001250 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1251 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001252 f.write("[[[ %s ]]] %s\n" % (v, k))
1253 if not v and first_line is None:
1254 # position cursor on first line with no password.
1255 first_line = i + 4
1256 f.close()
1257
1258 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1259 _, _ = p.communicate()
1260
1261 return self.ReadFile()
1262
1263 def ReadFile(self):
1264 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001265 if self.pwfile is None:
1266 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001267 try:
1268 f = open(self.pwfile, "r")
1269 for line in f:
1270 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001271 if not line or line[0] == '#':
1272 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001273 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1274 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001275 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001276 else:
1277 result[m.group(2)] = m.group(1)
1278 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001279 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001280 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001281 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001282 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001283
1284
Dan Albert8e0178d2015-01-27 15:53:15 -08001285def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1286 compress_type=None):
1287 import datetime
1288
1289 # http://b/18015246
1290 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1291 # for files larger than 2GiB. We can work around this by adjusting their
1292 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1293 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1294 # it isn't clear to me exactly what circumstances cause this).
1295 # `zipfile.write()` must be used directly to work around this.
1296 #
1297 # This mess can be avoided if we port to python3.
1298 saved_zip64_limit = zipfile.ZIP64_LIMIT
1299 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1300
1301 if compress_type is None:
1302 compress_type = zip_file.compression
1303 if arcname is None:
1304 arcname = filename
1305
1306 saved_stat = os.stat(filename)
1307
1308 try:
1309 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1310 # file to be zipped and reset it when we're done.
1311 os.chmod(filename, perms)
1312
1313 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001314 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1315 # intentional. zip stores datetimes in local time without a time zone
1316 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1317 # in the zip archive.
1318 local_epoch = datetime.datetime.fromtimestamp(0)
1319 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001320 os.utime(filename, (timestamp, timestamp))
1321
1322 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1323 finally:
1324 os.chmod(filename, saved_stat.st_mode)
1325 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1326 zipfile.ZIP64_LIMIT = saved_zip64_limit
1327
1328
Tao Bao58c1b962015-05-20 09:32:18 -07001329def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001330 compress_type=None):
1331 """Wrap zipfile.writestr() function to work around the zip64 limit.
1332
1333 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1334 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1335 when calling crc32(bytes).
1336
1337 But it still works fine to write a shorter string into a large zip file.
1338 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1339 when we know the string won't be too long.
1340 """
1341
1342 saved_zip64_limit = zipfile.ZIP64_LIMIT
1343 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1344
1345 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1346 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001347 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001348 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001349 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001350 else:
Tao Baof3282b42015-04-01 11:21:55 -07001351 zinfo = zinfo_or_arcname
1352
1353 # If compress_type is given, it overrides the value in zinfo.
1354 if compress_type is not None:
1355 zinfo.compress_type = compress_type
1356
Tao Bao58c1b962015-05-20 09:32:18 -07001357 # If perms is given, it has a priority.
1358 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001359 # If perms doesn't set the file type, mark it as a regular file.
1360 if perms & 0o770000 == 0:
1361 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001362 zinfo.external_attr = perms << 16
1363
Tao Baof3282b42015-04-01 11:21:55 -07001364 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001365 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1366
Dan Albert8b72aef2015-03-23 19:13:21 -07001367 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001368 zipfile.ZIP64_LIMIT = saved_zip64_limit
1369
1370
Tao Bao89d7ab22017-12-14 17:05:33 -08001371def ZipDelete(zip_filename, entries):
1372 """Deletes entries from a ZIP file.
1373
1374 Since deleting entries from a ZIP file is not supported, it shells out to
1375 'zip -d'.
1376
1377 Args:
1378 zip_filename: The name of the ZIP file.
1379 entries: The name of the entry, or the list of names to be deleted.
1380
1381 Raises:
1382 AssertionError: In case of non-zero return from 'zip'.
1383 """
1384 if isinstance(entries, basestring):
1385 entries = [entries]
1386 cmd = ["zip", "-d", zip_filename] + entries
1387 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1388 stdoutdata, _ = proc.communicate()
1389 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1390 stdoutdata)
1391
1392
Tao Baof3282b42015-04-01 11:21:55 -07001393def ZipClose(zip_file):
1394 # http://b/18015246
1395 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1396 # central directory.
1397 saved_zip64_limit = zipfile.ZIP64_LIMIT
1398 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1399
1400 zip_file.close()
1401
1402 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001403
1404
1405class DeviceSpecificParams(object):
1406 module = None
1407 def __init__(self, **kwargs):
1408 """Keyword arguments to the constructor become attributes of this
1409 object, which is passed to all functions in the device-specific
1410 module."""
1411 for k, v in kwargs.iteritems():
1412 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001413 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001414
1415 if self.module is None:
1416 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001417 if not path:
1418 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001419 try:
1420 if os.path.isdir(path):
1421 info = imp.find_module("releasetools", [path])
1422 else:
1423 d, f = os.path.split(path)
1424 b, x = os.path.splitext(f)
1425 if x == ".py":
1426 f = b
1427 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001428 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001429 self.module = imp.load_module("device_specific", *info)
1430 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001431 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001432
1433 def _DoCall(self, function_name, *args, **kwargs):
1434 """Call the named function in the device-specific module, passing
1435 the given args and kwargs. The first argument to the call will be
1436 the DeviceSpecific object itself. If there is no module, or the
1437 module does not define the function, return the value of the
1438 'default' kwarg (which itself defaults to None)."""
1439 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001440 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001441 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1442
1443 def FullOTA_Assertions(self):
1444 """Called after emitting the block of assertions at the top of a
1445 full OTA package. Implementations can add whatever additional
1446 assertions they like."""
1447 return self._DoCall("FullOTA_Assertions")
1448
Doug Zongkere5ff5902012-01-17 10:55:37 -08001449 def FullOTA_InstallBegin(self):
1450 """Called at the start of full OTA installation."""
1451 return self._DoCall("FullOTA_InstallBegin")
1452
Doug Zongker05d3dea2009-06-22 11:32:31 -07001453 def FullOTA_InstallEnd(self):
1454 """Called at the end of full OTA installation; typically this is
1455 used to install the image for the device's baseband processor."""
1456 return self._DoCall("FullOTA_InstallEnd")
1457
1458 def IncrementalOTA_Assertions(self):
1459 """Called after emitting the block of assertions at the top of an
1460 incremental OTA package. Implementations can add whatever
1461 additional assertions they like."""
1462 return self._DoCall("IncrementalOTA_Assertions")
1463
Doug Zongkere5ff5902012-01-17 10:55:37 -08001464 def IncrementalOTA_VerifyBegin(self):
1465 """Called at the start of the verification phase of incremental
1466 OTA installation; additional checks can be placed here to abort
1467 the script before any changes are made."""
1468 return self._DoCall("IncrementalOTA_VerifyBegin")
1469
Doug Zongker05d3dea2009-06-22 11:32:31 -07001470 def IncrementalOTA_VerifyEnd(self):
1471 """Called at the end of the verification phase of incremental OTA
1472 installation; additional checks can be placed here to abort the
1473 script before any changes are made."""
1474 return self._DoCall("IncrementalOTA_VerifyEnd")
1475
Doug Zongkere5ff5902012-01-17 10:55:37 -08001476 def IncrementalOTA_InstallBegin(self):
1477 """Called at the start of incremental OTA installation (after
1478 verification is complete)."""
1479 return self._DoCall("IncrementalOTA_InstallBegin")
1480
Doug Zongker05d3dea2009-06-22 11:32:31 -07001481 def IncrementalOTA_InstallEnd(self):
1482 """Called at the end of incremental OTA installation; typically
1483 this is used to install the image for the device's baseband
1484 processor."""
1485 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001486
Tao Bao9bc6bb22015-11-09 16:58:28 -08001487 def VerifyOTA_Assertions(self):
1488 return self._DoCall("VerifyOTA_Assertions")
1489
Tao Bao76def242017-11-21 09:25:31 -08001490
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001491class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001492 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001493 self.name = name
1494 self.data = data
1495 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001496 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001497 self.sha1 = sha1(data).hexdigest()
1498
1499 @classmethod
1500 def FromLocalFile(cls, name, diskname):
1501 f = open(diskname, "rb")
1502 data = f.read()
1503 f.close()
1504 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001505
1506 def WriteToTemp(self):
1507 t = tempfile.NamedTemporaryFile()
1508 t.write(self.data)
1509 t.flush()
1510 return t
1511
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001512 def WriteToDir(self, d):
1513 with open(os.path.join(d, self.name), "wb") as fp:
1514 fp.write(self.data)
1515
Geremy Condra36bd3652014-02-06 19:45:10 -08001516 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001517 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001518
Tao Bao76def242017-11-21 09:25:31 -08001519
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001520DIFF_PROGRAM_BY_EXT = {
1521 ".gz" : "imgdiff",
1522 ".zip" : ["imgdiff", "-z"],
1523 ".jar" : ["imgdiff", "-z"],
1524 ".apk" : ["imgdiff", "-z"],
1525 ".img" : "imgdiff",
1526 }
1527
Tao Bao76def242017-11-21 09:25:31 -08001528
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001529class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001530 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001531 self.tf = tf
1532 self.sf = sf
1533 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001534 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001535
1536 def ComputePatch(self):
1537 """Compute the patch (as a string of data) needed to turn sf into
1538 tf. Returns the same tuple as GetPatch()."""
1539
1540 tf = self.tf
1541 sf = self.sf
1542
Doug Zongker24cd2802012-08-14 16:36:15 -07001543 if self.diff_program:
1544 diff_program = self.diff_program
1545 else:
1546 ext = os.path.splitext(tf.name)[1]
1547 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001548
1549 ttemp = tf.WriteToTemp()
1550 stemp = sf.WriteToTemp()
1551
1552 ext = os.path.splitext(tf.name)[1]
1553
1554 try:
1555 ptemp = tempfile.NamedTemporaryFile()
1556 if isinstance(diff_program, list):
1557 cmd = copy.copy(diff_program)
1558 else:
1559 cmd = [diff_program]
1560 cmd.append(stemp.name)
1561 cmd.append(ttemp.name)
1562 cmd.append(ptemp.name)
1563 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001564 err = []
1565 def run():
1566 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001567 if e:
1568 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001569 th = threading.Thread(target=run)
1570 th.start()
1571 th.join(timeout=300) # 5 mins
1572 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001573 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001574 p.terminate()
1575 th.join(5)
1576 if th.is_alive():
1577 p.kill()
1578 th.join()
1579
Tianjie Xua2a9f992018-01-05 15:15:54 -08001580 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001581 print("WARNING: failure running %s:\n%s\n" % (
1582 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001583 self.patch = None
1584 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001585 diff = ptemp.read()
1586 finally:
1587 ptemp.close()
1588 stemp.close()
1589 ttemp.close()
1590
1591 self.patch = diff
1592 return self.tf, self.sf, self.patch
1593
1594
1595 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001596 """Returns a tuple of (target_file, source_file, patch_data).
1597
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001598 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001599 computing the patch failed.
1600 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001601 return self.tf, self.sf, self.patch
1602
1603
1604def ComputeDifferences(diffs):
1605 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001606 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001607
1608 # Do the largest files first, to try and reduce the long-pole effect.
1609 by_size = [(i.tf.size, i) for i in diffs]
1610 by_size.sort(reverse=True)
1611 by_size = [i[1] for i in by_size]
1612
1613 lock = threading.Lock()
1614 diff_iter = iter(by_size) # accessed under lock
1615
1616 def worker():
1617 try:
1618 lock.acquire()
1619 for d in diff_iter:
1620 lock.release()
1621 start = time.time()
1622 d.ComputePatch()
1623 dur = time.time() - start
1624 lock.acquire()
1625
1626 tf, sf, patch = d.GetPatch()
1627 if sf.name == tf.name:
1628 name = tf.name
1629 else:
1630 name = "%s (%s)" % (tf.name, sf.name)
1631 if patch is None:
Tao Bao76def242017-11-21 09:25:31 -08001632 print(
1633 "patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001634 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001635 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1636 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001637 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001638 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001639 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001640 raise
1641
1642 # start worker threads; wait for them all to finish.
1643 threads = [threading.Thread(target=worker)
1644 for i in range(OPTIONS.worker_threads)]
1645 for th in threads:
1646 th.start()
1647 while threads:
1648 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001649
1650
Dan Albert8b72aef2015-03-23 19:13:21 -07001651class BlockDifference(object):
1652 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001653 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001654 self.tgt = tgt
1655 self.src = src
1656 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001657 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001658 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001659
Tao Baodd2a5892015-03-12 12:32:37 -07001660 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001661 version = max(
1662 int(i) for i in
1663 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001664 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001665 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001666
1667 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001668 version=self.version,
1669 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001670 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001671 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001672 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001673 self.touched_src_ranges = b.touched_src_ranges
1674 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001675
Tao Baoaac4ad52015-10-16 15:26:34 -07001676 if src is None:
1677 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1678 else:
1679 _, self.device = GetTypeAndDevice("/" + partition,
1680 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001681
Tao Baod8d14be2016-02-04 14:26:02 -08001682 @property
1683 def required_cache(self):
1684 return self._required_cache
1685
Tao Bao76def242017-11-21 09:25:31 -08001686 def WriteScript(self, script, output_zip, progress=None,
1687 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001688 if not self.src:
1689 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001690 script.Print("Patching %s image unconditionally..." % (self.partition,))
1691 else:
1692 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001693
Dan Albert8b72aef2015-03-23 19:13:21 -07001694 if progress:
1695 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001696 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001697
1698 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001699 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001700
Tao Bao9bc6bb22015-11-09 16:58:28 -08001701 def WriteStrictVerifyScript(self, script):
1702 """Verify all the blocks in the care_map, including clobbered blocks.
1703
1704 This differs from the WriteVerifyScript() function: a) it prints different
1705 error messages; b) it doesn't allow half-way updated images to pass the
1706 verification."""
1707
1708 partition = self.partition
1709 script.Print("Verifying %s..." % (partition,))
1710 ranges = self.tgt.care_map
1711 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001712 script.AppendExtra(
1713 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1714 'ui_print("\\"%s\\" has unexpected contents.");' % (
1715 self.device, ranges_str,
1716 self.tgt.TotalSha1(include_clobbered_blocks=True),
1717 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001718 script.AppendExtra("")
1719
Tao Baod522bdc2016-04-12 15:53:16 -07001720 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001721 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001722
1723 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001724 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001725 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001726
1727 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001728 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001729 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001730 ranges = self.touched_src_ranges
1731 expected_sha1 = self.touched_src_sha1
1732 else:
1733 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1734 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001735
1736 # No blocks to be checked, skipping.
1737 if not ranges:
1738 return
1739
Tao Bao5ece99d2015-05-12 11:42:31 -07001740 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001741 script.AppendExtra(
1742 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1743 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1744 '"%s.patch.dat")) then' % (
1745 self.device, ranges_str, expected_sha1,
1746 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001747 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001748 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001749
Tianjie Xufc3422a2015-12-15 11:53:59 -08001750 if self.version >= 4:
1751
1752 # Bug: 21124327
1753 # When generating incrementals for the system and vendor partitions in
1754 # version 4 or newer, explicitly check the first block (which contains
1755 # the superblock) of the partition to see if it's what we expect. If
1756 # this check fails, give an explicit log message about the partition
1757 # having been remounted R/W (the most likely explanation).
1758 if self.check_first_block:
1759 script.AppendExtra('check_first_block("%s");' % (self.device,))
1760
1761 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001762 if partition == "system":
1763 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1764 else:
1765 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001766 script.AppendExtra((
1767 'ifelse (block_image_recover("{device}", "{ranges}") && '
1768 'block_image_verify("{device}", '
1769 'package_extract_file("{partition}.transfer.list"), '
1770 '"{partition}.new.dat", "{partition}.patch.dat"), '
1771 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001772 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001773 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001774 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001775
Tao Baodd2a5892015-03-12 12:32:37 -07001776 # Abort the OTA update. Note that the incremental OTA cannot be applied
1777 # even if it may match the checksum of the target partition.
1778 # a) If version < 3, operations like move and erase will make changes
1779 # unconditionally and damage the partition.
1780 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001781 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001782 if partition == "system":
1783 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1784 else:
1785 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1786 script.AppendExtra((
1787 'abort("E%d: %s partition has unexpected contents");\n'
1788 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001789
Tao Bao5fcaaef2015-06-01 13:40:49 -07001790 def _WritePostInstallVerifyScript(self, script):
1791 partition = self.partition
1792 script.Print('Verifying the updated %s image...' % (partition,))
1793 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1794 ranges = self.tgt.care_map
1795 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001796 script.AppendExtra(
1797 'if range_sha1("%s", "%s") == "%s" then' % (
1798 self.device, ranges_str,
1799 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001800
1801 # Bug: 20881595
1802 # Verify that extended blocks are really zeroed out.
1803 if self.tgt.extended:
1804 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001805 script.AppendExtra(
1806 'if range_sha1("%s", "%s") == "%s" then' % (
1807 self.device, ranges_str,
1808 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001809 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001810 if partition == "system":
1811 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1812 else:
1813 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001814 script.AppendExtra(
1815 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001816 ' abort("E%d: %s partition has unexpected non-zero contents after '
1817 'OTA update");\n'
1818 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001819 else:
1820 script.Print('Verified the updated %s image.' % (partition,))
1821
Tianjie Xu209db462016-05-24 17:34:52 -07001822 if partition == "system":
1823 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1824 else:
1825 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1826
Tao Bao5fcaaef2015-06-01 13:40:49 -07001827 script.AppendExtra(
1828 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001829 ' abort("E%d: %s partition has unexpected contents after OTA '
1830 'update");\n'
1831 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001832
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001833 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001834 ZipWrite(output_zip,
1835 '{}.transfer.list'.format(self.path),
1836 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001837
Tao Bao76def242017-11-21 09:25:31 -08001838 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1839 # its size. Quailty 9 almost triples the compression time but doesn't
1840 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001841 # zip | brotli(quality 6) | brotli(quality 9)
1842 # compressed_size: 942M | 869M (~8% reduced) | 854M
1843 # compression_time: 75s | 265s | 719s
1844 # decompression_time: 15s | 25s | 25s
1845
1846 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001847 brotli_cmd = ['brotli', '--quality=6',
1848 '--output={}.new.dat.br'.format(self.path),
1849 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001850 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao80921982018-03-21 21:02:19 -07001851 p = Run(brotli_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1852 stdoutdata, _ = p.communicate()
1853 assert p.returncode == 0, \
1854 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1855 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001856
1857 new_data_name = '{}.new.dat.br'.format(self.partition)
1858 ZipWrite(output_zip,
1859 '{}.new.dat.br'.format(self.path),
1860 new_data_name,
1861 compress_type=zipfile.ZIP_STORED)
1862 else:
1863 new_data_name = '{}.new.dat'.format(self.partition)
1864 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1865
Dan Albert8e0178d2015-01-27 15:53:15 -08001866 ZipWrite(output_zip,
1867 '{}.patch.dat'.format(self.path),
1868 '{}.patch.dat'.format(self.partition),
1869 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001870
Tianjie Xu209db462016-05-24 17:34:52 -07001871 if self.partition == "system":
1872 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1873 else:
1874 code = ErrorCode.VENDOR_UPDATE_FAILURE
1875
Dan Albert8e0178d2015-01-27 15:53:15 -08001876 call = ('block_image_update("{device}", '
1877 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001878 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001879 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001880 device=self.device, partition=self.partition,
1881 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001882 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001883
Dan Albert8b72aef2015-03-23 19:13:21 -07001884 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001885 data = source.ReadRangeSet(ranges)
1886 ctx = sha1()
1887
1888 for p in data:
1889 ctx.update(p)
1890
1891 return ctx.hexdigest()
1892
Tao Baoe9b61912015-07-09 17:37:49 -07001893 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1894 """Return the hash value for all zero blocks."""
1895 zero_block = '\x00' * 4096
1896 ctx = sha1()
1897 for _ in range(num_blocks):
1898 ctx.update(zero_block)
1899
1900 return ctx.hexdigest()
1901
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001902
1903DataImage = blockimgdiff.DataImage
1904
Tao Bao76def242017-11-21 09:25:31 -08001905
Doug Zongker96a57e72010-09-26 14:57:41 -07001906# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001907PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001908 "ext4": "EMMC",
1909 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001910 "f2fs": "EMMC",
1911 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001912}
Doug Zongker96a57e72010-09-26 14:57:41 -07001913
Tao Bao76def242017-11-21 09:25:31 -08001914
Doug Zongker96a57e72010-09-26 14:57:41 -07001915def GetTypeAndDevice(mount_point, info):
1916 fstab = info["fstab"]
1917 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001918 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1919 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001920 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001921 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001922
1923
1924def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001925 """Parses and converts a PEM-encoded certificate into DER-encoded.
1926
1927 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1928
1929 Returns:
1930 The decoded certificate string.
1931 """
1932 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001933 save = False
1934 for line in data.split("\n"):
1935 if "--END CERTIFICATE--" in line:
1936 break
1937 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001938 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001939 if "--BEGIN CERTIFICATE--" in line:
1940 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001941 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001942 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001943
Tao Bao04e1f012018-02-04 12:13:35 -08001944
1945def ExtractPublicKey(cert):
1946 """Extracts the public key (PEM-encoded) from the given certificate file.
1947
1948 Args:
1949 cert: The certificate filename.
1950
1951 Returns:
1952 The public key string.
1953
1954 Raises:
1955 AssertionError: On non-zero return from 'openssl'.
1956 """
1957 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1958 # While openssl 1.1 writes the key into the given filename followed by '-out',
1959 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1960 # stdout instead.
1961 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1962 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1963 pubkey, stderrdata = proc.communicate()
1964 assert proc.returncode == 0, \
1965 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1966 return pubkey
1967
1968
Doug Zongker412c02f2014-02-13 10:58:24 -08001969def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1970 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001971 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001972
Tao Bao6d5d6232018-03-09 17:04:42 -08001973 Most of the space in the boot and recovery images is just the kernel, which is
1974 identical for the two, so the resulting patch should be efficient. Add it to
1975 the output zip, along with a shell script that is run from init.rc on first
1976 boot to actually do the patching and install the new recovery image.
1977
1978 Args:
1979 input_dir: The top-level input directory of the target-files.zip.
1980 output_sink: The callback function that writes the result.
1981 recovery_img: File object for the recovery image.
1982 boot_img: File objects for the boot image.
1983 info_dict: A dict returned by common.LoadInfoDict() on the input
1984 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001985 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001986 if info_dict is None:
1987 info_dict = OPTIONS.info_dict
1988
Tao Bao6d5d6232018-03-09 17:04:42 -08001989 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001990
Tao Baof2cffbd2015-07-22 12:33:18 -07001991 if full_recovery_image:
1992 output_sink("etc/recovery.img", recovery_img.data)
1993
1994 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08001995 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07001996 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08001997 # With system-root-image, boot and recovery images will have mismatching
1998 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
1999 # to handle such a case.
2000 if system_root_image:
2001 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002002 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002003 assert not os.path.exists(path)
2004 else:
2005 diff_program = ["imgdiff"]
2006 if os.path.exists(path):
2007 diff_program.append("-b")
2008 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002009 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002010 else:
2011 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002012
2013 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2014 _, _, patch = d.ComputePatch()
2015 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002016
Dan Albertebb19aa2015-03-27 19:11:53 -07002017 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002018 # The following GetTypeAndDevice()s need to use the path in the target
2019 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002020 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2021 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2022 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002023 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002024
Tao Baof2cffbd2015-07-22 12:33:18 -07002025 if full_recovery_image:
2026 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002027if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2028 applypatch \\
2029 --flash /system/etc/recovery.img \\
2030 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2031 log -t recovery "Installing new recovery image: succeeded" || \\
2032 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002033else
2034 log -t recovery "Recovery image already installed"
2035fi
2036""" % {'type': recovery_type,
2037 'device': recovery_device,
2038 'sha1': recovery_img.sha1,
2039 'size': recovery_img.size}
2040 else:
2041 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002042if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2043 applypatch %(bonus_args)s \\
2044 --patch /system/recovery-from-boot.p \\
2045 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2046 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2047 log -t recovery "Installing new recovery image: succeeded" || \\
2048 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002049else
2050 log -t recovery "Recovery image already installed"
2051fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002052""" % {'boot_size': boot_img.size,
2053 'boot_sha1': boot_img.sha1,
2054 'recovery_size': recovery_img.size,
2055 'recovery_sha1': recovery_img.sha1,
2056 'boot_type': boot_type,
2057 'boot_device': boot_device,
2058 'recovery_type': recovery_type,
2059 'recovery_device': recovery_device,
2060 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002061
2062 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002063 # in the L release.
2064 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002065
Tao Bao89fbb0f2017-01-10 10:47:58 -08002066 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002067
2068 output_sink(sh_location, sh)