blob: 4e2346cb980dac6bf3087990d77b80b530243e6e [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 Xu861f4132018-09-12 11:49:33 -070085# Partitions that should have their care_map added to META/care_map.pb
86PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
87 'odm')
88
89
Tianjie Xu209db462016-05-24 17:34:52 -070090class ErrorCode(object):
91 """Define error_codes for failures that happen during the actual
92 update package installation.
93
94 Error codes 0-999 are reserved for failures before the package
95 installation (i.e. low battery, package verification failure).
96 Detailed code in 'bootable/recovery/error_code.h' """
97
98 SYSTEM_VERIFICATION_FAILURE = 1000
99 SYSTEM_UPDATE_FAILURE = 1001
100 SYSTEM_UNEXPECTED_CONTENTS = 1002
101 SYSTEM_NONZERO_CONTENTS = 1003
102 SYSTEM_RECOVER_FAILURE = 1004
103 VENDOR_VERIFICATION_FAILURE = 2000
104 VENDOR_UPDATE_FAILURE = 2001
105 VENDOR_UNEXPECTED_CONTENTS = 2002
106 VENDOR_NONZERO_CONTENTS = 2003
107 VENDOR_RECOVER_FAILURE = 2004
108 OEM_PROP_MISMATCH = 3000
109 FINGERPRINT_MISMATCH = 3001
110 THUMBPRINT_MISMATCH = 3002
111 OLDER_BUILD = 3003
112 DEVICE_MISMATCH = 3004
113 BAD_PATCH_FILE = 3005
114 INSUFFICIENT_CACHE_SPACE = 3006
115 TUNE_PARTITION_FAILURE = 3007
116 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800117
Tao Bao80921982018-03-21 21:02:19 -0700118
Dan Albert8b72aef2015-03-23 19:13:21 -0700119class ExternalError(RuntimeError):
120 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700121
122
Tao Bao39451582017-05-04 11:10:47 -0700123def Run(args, verbose=None, **kwargs):
124 """Create and return a subprocess.Popen object.
125
126 Caller can specify if the command line should be printed. The global
127 OPTIONS.verbose will be used if not specified.
128 """
129 if verbose is None:
130 verbose = OPTIONS.verbose
131 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800132 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700133 return subprocess.Popen(args, **kwargs)
134
135
Tao Baoc765cca2018-01-31 17:32:40 -0800136def RoundUpTo4K(value):
137 rounded_up = value + 4095
138 return rounded_up - (rounded_up % 4096)
139
140
Ying Wang7e6d4e42010-12-13 16:25:36 -0800141def CloseInheritedPipes():
142 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
143 before doing other work."""
144 if platform.system() != "Darwin":
145 return
146 for d in range(3, 1025):
147 try:
148 stat = os.fstat(d)
149 if stat is not None:
150 pipebit = stat[0] & 0x1000
151 if pipebit != 0:
152 os.close(d)
153 except OSError:
154 pass
155
156
Tao Bao410ad8b2018-08-24 12:08:38 -0700157def LoadInfoDict(input_file, repacking=False):
158 """Loads the key/value pairs from the given input target_files.
159
160 It reads `META/misc_info.txt` file in the target_files input, does sanity
161 checks and returns the parsed key/value pairs for to the given build. It's
162 usually called early when working on input target_files files, e.g. when
163 generating OTAs, or signing builds. Note that the function may be called
164 against an old target_files file (i.e. from past dessert releases). So the
165 property parsing needs to be backward compatible.
166
167 In a `META/misc_info.txt`, a few properties are stored as links to the files
168 in the PRODUCT_OUT directory. It works fine with the build system. However,
169 they are no longer available when (re)generating images from target_files zip.
170 When `repacking` is True, redirect these properties to the actual files in the
171 unzipped directory.
172
173 Args:
174 input_file: The input target_files file, which could be an open
175 zipfile.ZipFile instance, or a str for the dir that contains the files
176 unzipped from a target_files file.
177 repacking: Whether it's trying repack an target_files file after loading the
178 info dict (default: False). If so, it will rewrite a few loaded
179 properties (e.g. selinux_fc, root_dir) to point to the actual files in
180 target_files file. When doing repacking, `input_file` must be a dir.
181
182 Returns:
183 A dict that contains the parsed key/value pairs.
184
185 Raises:
186 AssertionError: On invalid input arguments.
187 ValueError: On malformed input values.
188 """
189 if repacking:
190 assert isinstance(input_file, str), \
191 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700192
Doug Zongkerc9253822014-02-04 12:17:58 -0800193 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700194 if isinstance(input_file, zipfile.ZipFile):
195 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800196 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700197 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800198 try:
199 with open(path) as f:
200 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700201 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800202 if e.errno == errno.ENOENT:
203 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800204
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700205 try:
Michael Runge6e836112014-04-15 17:40:21 -0700206 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700207 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700208 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700209
Tao Bao410ad8b2018-08-24 12:08:38 -0700210 if "recovery_api_version" not in d:
211 raise ValueError("Failed to find 'recovery_api_version'")
212 if "fstab_version" not in d:
213 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800214
Tao Bao410ad8b2018-08-24 12:08:38 -0700215 if repacking:
216 # We carry a copy of file_contexts.bin under META/. If not available, search
217 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
218 # images than the one running on device, in that case, we must have the one
219 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700220 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700221 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700222 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700223
Tom Cherryd14b8952018-08-09 14:26:00 -0700224 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700225
Tom Cherryd14b8952018-08-09 14:26:00 -0700226 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700227 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700228 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700229 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700230
Tao Baof54216f2016-03-29 15:12:37 -0700231 # Redirect {system,vendor}_base_fs_file.
232 if "system_base_fs_file" in d:
233 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700234 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700235 if os.path.exists(system_base_fs_file):
236 d["system_base_fs_file"] = system_base_fs_file
237 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800238 print("Warning: failed to find system base fs file: %s" % (
239 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700240 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700241
242 if "vendor_base_fs_file" in d:
243 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700244 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700245 if os.path.exists(vendor_base_fs_file):
246 d["vendor_base_fs_file"] = vendor_base_fs_file
247 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800248 print("Warning: failed to find vendor base fs file: %s" % (
249 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700250 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700251
Doug Zongker37974732010-09-16 17:44:38 -0700252 def makeint(key):
253 if key in d:
254 d[key] = int(d[key], 0)
255
256 makeint("recovery_api_version")
257 makeint("blocksize")
258 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700259 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700260 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700261 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700262 makeint("recovery_size")
263 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800264 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700265
Tao Baoa57ab9f2018-08-24 12:08:38 -0700266 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
267 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
268 # cases, since it may load the info_dict from an old build (e.g. when
269 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800270 system_root_image = d.get("system_root_image") == "true"
271 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700272 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700273 if isinstance(input_file, zipfile.ZipFile):
274 if recovery_fstab_path not in input_file.namelist():
275 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
276 else:
277 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
278 if not os.path.exists(path):
279 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800280 d["fstab"] = LoadRecoveryFSTab(
281 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700282
Tao Bao76def242017-11-21 09:25:31 -0800283 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700284 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700285 if isinstance(input_file, zipfile.ZipFile):
286 if recovery_fstab_path not in input_file.namelist():
287 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
288 else:
289 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
290 if not os.path.exists(path):
291 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800292 d["fstab"] = LoadRecoveryFSTab(
293 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700294
Tianjie Xucfa86222016-03-07 16:31:19 -0800295 else:
296 d["fstab"] = None
297
Tianjie Xu861f4132018-09-12 11:49:33 -0700298 # Tries to load the build props for all partitions with care_map, including
299 # system and vendor.
300 for partition in PARTITIONS_WITH_CARE_MAP:
301 d["{}.build.prop".format(partition)] = LoadBuildProp(
302 read_helper, "{}/build.prop".format(partition.upper()))
303 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800304
305 # Set up the salt (based on fingerprint or thumbprint) that will be used when
306 # adding AVB footer.
307 if d.get("avb_enable") == "true":
308 fp = None
309 if "build.prop" in d:
310 build_prop = d["build.prop"]
311 if "ro.build.fingerprint" in build_prop:
312 fp = build_prop["ro.build.fingerprint"]
313 elif "ro.build.thumbprint" in build_prop:
314 fp = build_prop["ro.build.thumbprint"]
315 if fp:
316 d["avb_salt"] = sha256(fp).hexdigest()
317
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700318 return d
319
Tao Baod1de6f32017-03-01 16:38:48 -0800320
Tao Baobcd1d162017-08-26 13:10:26 -0700321def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700322 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700323 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700324 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700325 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700326 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700327 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700328
Tao Baod1de6f32017-03-01 16:38:48 -0800329
Michael Runge6e836112014-04-15 17:40:21 -0700330def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700331 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700332 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700333 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700334 if not line or line.startswith("#"):
335 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700336 if "=" in line:
337 name, value = line.split("=", 1)
338 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700339 return d
340
Tao Baod1de6f32017-03-01 16:38:48 -0800341
Tianjie Xucfa86222016-03-07 16:31:19 -0800342def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
343 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700344 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800345 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700346 self.mount_point = mount_point
347 self.fs_type = fs_type
348 self.device = device
349 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700350 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700351
352 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800353 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700354 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800355 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700356 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700357
Tao Baod1de6f32017-03-01 16:38:48 -0800358 assert fstab_version == 2
359
360 d = {}
361 for line in data.split("\n"):
362 line = line.strip()
363 if not line or line.startswith("#"):
364 continue
365
366 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
367 pieces = line.split()
368 if len(pieces) != 5:
369 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
370
371 # Ignore entries that are managed by vold.
372 options = pieces[4]
373 if "voldmanaged=" in options:
374 continue
375
376 # It's a good line, parse it.
377 length = 0
378 options = options.split(",")
379 for i in options:
380 if i.startswith("length="):
381 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800382 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800383 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700384 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800385
Tao Baod1de6f32017-03-01 16:38:48 -0800386 mount_flags = pieces[3]
387 # Honor the SELinux context if present.
388 context = None
389 for i in mount_flags.split(","):
390 if i.startswith("context="):
391 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800392
Tao Baod1de6f32017-03-01 16:38:48 -0800393 mount_point = pieces[1]
394 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
395 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800396
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700397 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700398 # system. Other areas assume system is always at "/system" so point /system
399 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700400 if system_root_image:
401 assert not d.has_key("/system") and d.has_key("/")
402 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700403 return d
404
405
Doug Zongker37974732010-09-16 17:44:38 -0700406def DumpInfoDict(d):
407 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800408 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700409
Dan Albert8b72aef2015-03-23 19:13:21 -0700410
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800411def AppendAVBSigningArgs(cmd, partition):
412 """Append signing arguments for avbtool."""
413 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
414 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
415 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
416 if key_path and algorithm:
417 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700418 avb_salt = OPTIONS.info_dict.get("avb_salt")
419 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700420 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700421 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800422
423
Tao Bao02a08592018-07-22 12:40:45 -0700424def GetAvbChainedPartitionArg(partition, info_dict, key=None):
425 """Constructs and returns the arg to build or verify a chained partition.
426
427 Args:
428 partition: The partition name.
429 info_dict: The info dict to look up the key info and rollback index
430 location.
431 key: The key to be used for building or verifying the partition. Defaults to
432 the key listed in info_dict.
433
434 Returns:
435 A string of form "partition:rollback_index_location:key" that can be used to
436 build or verify vbmeta image.
437
438 Raises:
439 AssertionError: When it fails to extract the public key with avbtool.
440 """
441 if key is None:
442 key = info_dict["avb_" + partition + "_key_path"]
443 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
444 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
445 proc = Run(
446 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path],
447 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
448 stdoutdata, _ = proc.communicate()
449 assert proc.returncode == 0, \
450 "Failed to extract pubkey for {}:\n{}".format(
451 partition, stdoutdata)
452
453 rollback_index_location = info_dict[
454 "avb_" + partition + "_rollback_index_location"]
455 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
456
457
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700458def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800459 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700460 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700461
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700462 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800463 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
464 we are building a two-step special image (i.e. building a recovery image to
465 be loaded into /boot in two-step OTAs).
466
467 Return the image data, or None if sourcedir does not appear to contains files
468 for building the requested image.
469 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700470
471 def make_ramdisk():
472 ramdisk_img = tempfile.NamedTemporaryFile()
473
474 if os.access(fs_config_file, os.F_OK):
475 cmd = ["mkbootfs", "-f", fs_config_file,
476 os.path.join(sourcedir, "RAMDISK")]
477 else:
478 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
479 p1 = Run(cmd, stdout=subprocess.PIPE)
480 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
481
482 p2.wait()
483 p1.wait()
484 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
485 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
486
487 return ramdisk_img
488
489 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
490 return None
491
492 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700493 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700494
Doug Zongkerd5131602012-08-02 14:46:42 -0700495 if info_dict is None:
496 info_dict = OPTIONS.info_dict
497
Doug Zongkereef39442009-04-02 12:14:19 -0700498 img = tempfile.NamedTemporaryFile()
499
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700500 if has_ramdisk:
501 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700502
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800503 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
504 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
505
506 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700507
Benoit Fradina45a8682014-07-14 21:00:43 +0200508 fn = os.path.join(sourcedir, "second")
509 if os.access(fn, os.F_OK):
510 cmd.append("--second")
511 cmd.append(fn)
512
Doug Zongker171f1cd2009-06-15 22:36:37 -0700513 fn = os.path.join(sourcedir, "cmdline")
514 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700515 cmd.append("--cmdline")
516 cmd.append(open(fn).read().rstrip("\n"))
517
518 fn = os.path.join(sourcedir, "base")
519 if os.access(fn, os.F_OK):
520 cmd.append("--base")
521 cmd.append(open(fn).read().rstrip("\n"))
522
Ying Wang4de6b5b2010-08-25 14:29:34 -0700523 fn = os.path.join(sourcedir, "pagesize")
524 if os.access(fn, os.F_OK):
525 cmd.append("--pagesize")
526 cmd.append(open(fn).read().rstrip("\n"))
527
Tao Bao76def242017-11-21 09:25:31 -0800528 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700529 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700530 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700531
Tao Bao76def242017-11-21 09:25:31 -0800532 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000533 if args and args.strip():
534 cmd.extend(shlex.split(args))
535
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700536 if has_ramdisk:
537 cmd.extend(["--ramdisk", ramdisk_img.name])
538
Tao Baod95e9fd2015-03-29 23:07:41 -0700539 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800540 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700541 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700542 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700543 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700544 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700545
Tao Baobf70c3182017-07-11 17:27:55 -0700546 # "boot" or "recovery", without extension.
547 partition_name = os.path.basename(sourcedir).lower()
548
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700549 if (partition_name == "recovery" and
550 info_dict.get("include_recovery_dtbo") == "true"):
551 fn = os.path.join(sourcedir, "recovery_dtbo")
552 cmd.extend(["--recovery_dtbo", fn])
553
Doug Zongker38a649f2009-06-17 09:07:09 -0700554 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700555 p.communicate()
Tao Baobf70c3182017-07-11 17:27:55 -0700556 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700557
Tao Bao76def242017-11-21 09:25:31 -0800558 if (info_dict.get("boot_signer") == "true" and
559 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800560 # Hard-code the path as "/boot" for two-step special recovery image (which
561 # will be loaded into /boot during the two-step OTA).
562 if two_step_image:
563 path = "/boot"
564 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700565 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700566 cmd = [OPTIONS.boot_signer_path]
567 cmd.extend(OPTIONS.boot_signer_args)
568 cmd.extend([path, img.name,
569 info_dict["verity_key"] + ".pk8",
570 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700571 p = Run(cmd, stdout=subprocess.PIPE)
572 p.communicate()
573 assert p.returncode == 0, "boot_signer of %s image failed" % path
574
Tao Baod95e9fd2015-03-29 23:07:41 -0700575 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800576 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700577 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700578 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800579 # We have switched from the prebuilt futility binary to using the tool
580 # (futility-host) built from the source. Override the setting in the old
581 # TF.zip.
582 futility = info_dict["futility"]
583 if futility.startswith("prebuilts/"):
584 futility = "futility-host"
585 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700586 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700587 info_dict["vboot_key"] + ".vbprivk",
588 info_dict["vboot_subkey"] + ".vbprivk",
589 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700590 img.name]
591 p = Run(cmd, stdout=subprocess.PIPE)
592 p.communicate()
593 assert p.returncode == 0, "vboot_signer of %s image failed" % path
594
Tao Baof3282b42015-04-01 11:21:55 -0700595 # Clean up the temp files.
596 img_unsigned.close()
597 img_keyblock.close()
598
David Zeuthen8fecb282017-12-01 16:24:01 -0500599 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800600 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700601 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500602 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400603 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700604 "--partition_size", str(part_size), "--partition_name",
605 partition_name]
606 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500607 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400608 if args and args.strip():
609 cmd.extend(shlex.split(args))
610 p = Run(cmd, stdout=subprocess.PIPE)
611 p.communicate()
612 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c3182017-07-11 17:27:55 -0700613 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500614
615 img.seek(os.SEEK_SET, 0)
616 data = img.read()
617
618 if has_ramdisk:
619 ramdisk_img.close()
620 img.close()
621
622 return data
623
624
Doug Zongkerd5131602012-08-02 14:46:42 -0700625def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800626 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700627 """Return a File object with the desired bootable image.
628
629 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
630 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
631 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700632
Doug Zongker55d93282011-01-25 17:03:34 -0800633 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
634 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800635 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800636 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700637
638 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
639 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800640 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700641 return File.FromLocalFile(name, prebuilt_path)
642
Tao Bao89fbb0f2017-01-10 10:47:58 -0800643 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700644
645 if info_dict is None:
646 info_dict = OPTIONS.info_dict
647
648 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800649 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
650 # for recovery.
651 has_ramdisk = (info_dict.get("system_root_image") != "true" or
652 prebuilt_name != "boot.img" or
653 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700654
Doug Zongker6f1d0312014-08-22 08:07:12 -0700655 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400656 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
657 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800658 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700659 if data:
660 return File(name, data)
661 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800662
Doug Zongkereef39442009-04-02 12:14:19 -0700663
Narayan Kamatha07bf042017-08-14 14:49:21 +0100664def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800665 """Gunzips the given gzip compressed file to a given output file."""
666 with gzip.open(in_filename, "rb") as in_file, \
667 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100668 shutil.copyfileobj(in_file, out_file)
669
670
Doug Zongker75f17362009-12-08 13:46:44 -0800671def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800672 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800673
Tao Bao1c830bf2017-12-25 10:43:47 -0800674 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
675 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800676
Tao Bao1c830bf2017-12-25 10:43:47 -0800677 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800678 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800679 """
Doug Zongkereef39442009-04-02 12:14:19 -0700680
Doug Zongker55d93282011-01-25 17:03:34 -0800681 def unzip_to_dir(filename, dirname):
682 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
683 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800684 cmd.extend(pattern)
Tao Bao80921982018-03-21 21:02:19 -0700685 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
686 stdoutdata, _ = p.communicate()
Doug Zongker55d93282011-01-25 17:03:34 -0800687 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700688 raise ExternalError(
689 "Failed to unzip input target-files \"{}\":\n{}".format(
690 filename, stdoutdata))
Doug Zongker55d93282011-01-25 17:03:34 -0800691
Tao Bao1c830bf2017-12-25 10:43:47 -0800692 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800693 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
694 if m:
695 unzip_to_dir(m.group(1), tmp)
696 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
697 filename = m.group(1)
698 else:
699 unzip_to_dir(filename, tmp)
700
Tao Baodba59ee2018-01-09 13:21:02 -0800701 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700702
703
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700704def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
705 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800706 """Returns a SparseImage object suitable for passing to BlockImageDiff.
707
708 This function loads the specified sparse image from the given path, and
709 performs additional processing for OTA purpose. For example, it always adds
710 block 0 to clobbered blocks list. It also detects files that cannot be
711 reconstructed from the block list, for whom we should avoid applying imgdiff.
712
713 Args:
714 which: The partition name, which must be "system" or "vendor".
715 tmpdir: The directory that contains the prebuilt image and block map file.
716 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800717 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700718 hashtree_info_generator: If present, generates the hashtree_info for this
719 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800720 Returns:
721 A SparseImage object, with file_map info loaded.
722 """
723 assert which in ("system", "vendor")
724
725 path = os.path.join(tmpdir, "IMAGES", which + ".img")
726 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
727
728 # The image and map files must have been created prior to calling
729 # ota_from_target_files.py (since LMP).
730 assert os.path.exists(path) and os.path.exists(mappath)
731
732 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
733 # it to clobbered_blocks so that it will be written to the target
734 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
735 clobbered_blocks = "0"
736
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700737 image = sparse_img.SparseImage(
738 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
739 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800740
741 # block.map may contain less blocks, because mke2fs may skip allocating blocks
742 # if they contain all zeros. We can't reconstruct such a file from its block
743 # list. Tag such entries accordingly. (Bug: 65213616)
744 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800745 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700746 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800747 continue
748
Tom Cherryd14b8952018-08-09 14:26:00 -0700749 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
750 # filename listed in system.map may contain an additional leading slash
751 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
752 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700753 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
754
Tom Cherryd14b8952018-08-09 14:26:00 -0700755 # Special handling another case, where files not under /system
756 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700757 if which == 'system' and not arcname.startswith('SYSTEM'):
758 arcname = 'ROOT/' + arcname
759
760 assert arcname in input_zip.namelist(), \
761 "Failed to find the ZIP entry for {}".format(entry)
762
Tao Baoc765cca2018-01-31 17:32:40 -0800763 info = input_zip.getinfo(arcname)
764 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800765
766 # If a RangeSet has been tagged as using shared blocks while loading the
767 # image, its block list must be already incomplete due to that reason. Don't
768 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
769 if ranges.extra.get('uses_shared_blocks'):
770 continue
771
Tao Baoc765cca2018-01-31 17:32:40 -0800772 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
773 ranges.extra['incomplete'] = True
774
775 return image
776
777
Doug Zongkereef39442009-04-02 12:14:19 -0700778def GetKeyPasswords(keylist):
779 """Given a list of keys, prompt the user to enter passwords for
780 those which require them. Return a {key: password} dict. password
781 will be None if the key has no password."""
782
Doug Zongker8ce7c252009-05-22 13:34:54 -0700783 no_passwords = []
784 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700785 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700786 devnull = open("/dev/null", "w+b")
787 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800788 # We don't need a password for things that aren't really keys.
789 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700790 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700791 continue
792
T.R. Fullhart37e10522013-03-18 10:31:26 -0700793 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700794 "-inform", "DER", "-nocrypt"],
795 stdin=devnull.fileno(),
796 stdout=devnull.fileno(),
797 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700798 p.communicate()
799 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700800 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700801 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700802 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700803 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
804 "-inform", "DER", "-passin", "pass:"],
805 stdin=devnull.fileno(),
806 stdout=devnull.fileno(),
807 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700808 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700809 if p.returncode == 0:
810 # Encrypted key with empty string as password.
811 key_passwords[k] = ''
812 elif stderr.startswith('Error decrypting key'):
813 # Definitely encrypted key.
814 # It would have said "Error reading key" if it didn't parse correctly.
815 need_passwords.append(k)
816 else:
817 # Potentially, a type of key that openssl doesn't understand.
818 # We'll let the routines in signapk.jar handle it.
819 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700820 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700821
T.R. Fullhart37e10522013-03-18 10:31:26 -0700822 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800823 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700824 return key_passwords
825
826
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800827def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700828 """Gets the minSdkVersion declared in the APK.
829
830 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
831 This can be both a decimal number (API Level) or a codename.
832
833 Args:
834 apk_name: The APK filename.
835
836 Returns:
837 The parsed SDK version string.
838
839 Raises:
840 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800841 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700842 proc = Run(
843 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
844 stderr=subprocess.PIPE)
845 stdoutdata, stderrdata = proc.communicate()
846 if proc.returncode != 0:
847 raise ExternalError(
848 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
849 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800850
Tao Baof47bf0f2018-03-21 23:28:51 -0700851 for line in stdoutdata.split("\n"):
852 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800853 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
854 if m:
855 return m.group(1)
856 raise ExternalError("No minSdkVersion returned by aapt")
857
858
859def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700860 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800861
Tao Baof47bf0f2018-03-21 23:28:51 -0700862 If minSdkVersion is set to a codename, it is translated to a number using the
863 provided map.
864
865 Args:
866 apk_name: The APK filename.
867
868 Returns:
869 The parsed SDK version number.
870
871 Raises:
872 ExternalError: On failing to get the min SDK version number.
873 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800874 version = GetMinSdkVersion(apk_name)
875 try:
876 return int(version)
877 except ValueError:
878 # Not a decimal number. Codename?
879 if version in codename_to_api_level_map:
880 return codename_to_api_level_map[version]
881 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700882 raise ExternalError(
883 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
884 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800885
886
887def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800888 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700889 """Sign the input_name zip/jar/apk, producing output_name. Use the
890 given key and password (the latter may be None if the key does not
891 have a password.
892
Doug Zongker951495f2009-08-14 12:44:19 -0700893 If whole_file is true, use the "-w" option to SignApk to embed a
894 signature that covers the whole file in the archive comment of the
895 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800896
897 min_api_level is the API Level (int) of the oldest platform this file may end
898 up on. If not specified for an APK, the API Level is obtained by interpreting
899 the minSdkVersion attribute of the APK's AndroidManifest.xml.
900
901 codename_to_api_level_map is needed to translate the codename which may be
902 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700903 """
Tao Bao76def242017-11-21 09:25:31 -0800904 if codename_to_api_level_map is None:
905 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700906
Alex Klyubin9667b182015-12-10 13:38:50 -0800907 java_library_path = os.path.join(
908 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
909
Tao Baoe95540e2016-11-08 12:08:53 -0800910 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
911 ["-Djava.library.path=" + java_library_path,
912 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
913 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700914 if whole_file:
915 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800916
917 min_sdk_version = min_api_level
918 if min_sdk_version is None:
919 if not whole_file:
920 min_sdk_version = GetMinSdkVersionInt(
921 input_name, codename_to_api_level_map)
922 if min_sdk_version is not None:
923 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
924
T.R. Fullhart37e10522013-03-18 10:31:26 -0700925 cmd.extend([key + OPTIONS.public_key_suffix,
926 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800927 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700928
Tao Bao80921982018-03-21 21:02:19 -0700929 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
930 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700931 if password is not None:
932 password += "\n"
Tao Bao80921982018-03-21 21:02:19 -0700933 stdoutdata, _ = p.communicate(password)
Doug Zongkereef39442009-04-02 12:14:19 -0700934 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700935 raise ExternalError(
936 "Failed to run signapk.jar: return code {}:\n{}".format(
937 p.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700938
Doug Zongkereef39442009-04-02 12:14:19 -0700939
Doug Zongker37974732010-09-16 17:44:38 -0700940def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800941 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700942
Tao Bao9dd909e2017-11-14 11:27:32 -0800943 For non-AVB images, raise exception if the data is too big. Print a warning
944 if the data is nearing the maximum size.
945
946 For AVB images, the actual image size should be identical to the limit.
947
948 Args:
949 data: A string that contains all the data for the partition.
950 target: The partition name. The ".img" suffix is optional.
951 info_dict: The dict to be looked up for relevant info.
952 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700953 if target.endswith(".img"):
954 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700955 mount_point = "/" + target
956
Ying Wangf8824af2014-06-03 14:07:27 -0700957 fs_type = None
958 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700959 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700960 if mount_point == "/userdata":
961 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700962 p = info_dict["fstab"][mount_point]
963 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800964 device = p.device
965 if "/" in device:
966 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -0800967 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -0700968 if not fs_type or not limit:
969 return
Doug Zongkereef39442009-04-02 12:14:19 -0700970
Andrew Boie0f9aec82012-02-14 09:32:52 -0800971 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800972 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
973 # path.
974 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
975 if size != limit:
976 raise ExternalError(
977 "Mismatching image size for %s: expected %d actual %d" % (
978 target, limit, size))
979 else:
980 pct = float(size) * 100.0 / limit
981 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
982 if pct >= 99.0:
983 raise ExternalError(msg)
984 elif pct >= 95.0:
985 print("\n WARNING: %s\n" % (msg,))
986 elif OPTIONS.verbose:
987 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700988
989
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800990def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800991 """Parses the APK certs info from a given target-files zip.
992
993 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
994 tuple with the following elements: (1) a dictionary that maps packages to
995 certs (based on the "certificate" and "private_key" attributes in the file;
996 (2) a string representing the extension of compressed APKs in the target files
997 (e.g ".gz", ".bro").
998
999 Args:
1000 tf_zip: The input target_files ZipFile (already open).
1001
1002 Returns:
1003 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1004 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1005 no compressed APKs.
1006 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001007 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001008 compressed_extension = None
1009
Tao Bao0f990332017-09-08 19:02:54 -07001010 # META/apkcerts.txt contains the info for _all_ the packages known at build
1011 # time. Filter out the ones that are not installed.
1012 installed_files = set()
1013 for name in tf_zip.namelist():
1014 basename = os.path.basename(name)
1015 if basename:
1016 installed_files.add(basename)
1017
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001018 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1019 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001020 if not line:
1021 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001022 m = re.match(
1023 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1024 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1025 line)
1026 if not m:
1027 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001028
Tao Bao818ddf52018-01-05 11:17:34 -08001029 matches = m.groupdict()
1030 cert = matches["CERT"]
1031 privkey = matches["PRIVKEY"]
1032 name = matches["NAME"]
1033 this_compressed_extension = matches["COMPRESSED"]
1034
1035 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1036 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1037 if cert in SPECIAL_CERT_STRINGS and not privkey:
1038 certmap[name] = cert
1039 elif (cert.endswith(OPTIONS.public_key_suffix) and
1040 privkey.endswith(OPTIONS.private_key_suffix) and
1041 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1042 certmap[name] = cert[:-public_key_suffix_len]
1043 else:
1044 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1045
1046 if not this_compressed_extension:
1047 continue
1048
1049 # Only count the installed files.
1050 filename = name + '.' + this_compressed_extension
1051 if filename not in installed_files:
1052 continue
1053
1054 # Make sure that all the values in the compression map have the same
1055 # extension. We don't support multiple compression methods in the same
1056 # system image.
1057 if compressed_extension:
1058 if this_compressed_extension != compressed_extension:
1059 raise ValueError(
1060 "Multiple compressed extensions: {} vs {}".format(
1061 compressed_extension, this_compressed_extension))
1062 else:
1063 compressed_extension = this_compressed_extension
1064
1065 return (certmap,
1066 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001067
1068
Doug Zongkereef39442009-04-02 12:14:19 -07001069COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001070Global options
1071
1072 -p (--path) <dir>
1073 Prepend <dir>/bin to the list of places to search for binaries run by this
1074 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001075
Doug Zongker05d3dea2009-06-22 11:32:31 -07001076 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001077 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001078
Tao Bao30df8b42018-04-23 15:32:53 -07001079 -x (--extra) <key=value>
1080 Add a key/value pair to the 'extras' dict, which device-specific extension
1081 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001082
Doug Zongkereef39442009-04-02 12:14:19 -07001083 -v (--verbose)
1084 Show command lines being executed.
1085
1086 -h (--help)
1087 Display this usage message and exit.
1088"""
1089
1090def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001091 print(docstring.rstrip("\n"))
1092 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001093
1094
1095def ParseOptions(argv,
1096 docstring,
1097 extra_opts="", extra_long_opts=(),
1098 extra_option_handler=None):
1099 """Parse the options in argv and return any arguments that aren't
1100 flags. docstring is the calling module's docstring, to be displayed
1101 for errors and -h. extra_opts and extra_long_opts are for flags
1102 defined by the caller, which are processed by passing them to
1103 extra_option_handler."""
1104
1105 try:
1106 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001107 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001108 ["help", "verbose", "path=", "signapk_path=",
1109 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001110 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001111 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1112 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001113 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001114 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001115 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001116 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001117 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001118 sys.exit(2)
1119
Doug Zongkereef39442009-04-02 12:14:19 -07001120 for o, a in opts:
1121 if o in ("-h", "--help"):
1122 Usage(docstring)
1123 sys.exit()
1124 elif o in ("-v", "--verbose"):
1125 OPTIONS.verbose = True
1126 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001127 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001128 elif o in ("--signapk_path",):
1129 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001130 elif o in ("--signapk_shared_library_path",):
1131 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001132 elif o in ("--extra_signapk_args",):
1133 OPTIONS.extra_signapk_args = shlex.split(a)
1134 elif o in ("--java_path",):
1135 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001136 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001137 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001138 elif o in ("--public_key_suffix",):
1139 OPTIONS.public_key_suffix = a
1140 elif o in ("--private_key_suffix",):
1141 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001142 elif o in ("--boot_signer_path",):
1143 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001144 elif o in ("--boot_signer_args",):
1145 OPTIONS.boot_signer_args = shlex.split(a)
1146 elif o in ("--verity_signer_path",):
1147 OPTIONS.verity_signer_path = a
1148 elif o in ("--verity_signer_args",):
1149 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001150 elif o in ("-s", "--device_specific"):
1151 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001152 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001153 key, value = a.split("=", 1)
1154 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001155 else:
1156 if extra_option_handler is None or not extra_option_handler(o, a):
1157 assert False, "unknown option \"%s\"" % (o,)
1158
Doug Zongker85448772014-09-09 14:59:20 -07001159 if OPTIONS.search_path:
1160 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1161 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001162
1163 return args
1164
1165
Tao Bao4c851b12016-09-19 13:54:38 -07001166def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001167 """Make a temp file and add it to the list of things to be deleted
1168 when Cleanup() is called. Return the filename."""
1169 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1170 os.close(fd)
1171 OPTIONS.tempfiles.append(fn)
1172 return fn
1173
1174
Tao Bao1c830bf2017-12-25 10:43:47 -08001175def MakeTempDir(prefix='tmp', suffix=''):
1176 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1177
1178 Returns:
1179 The absolute pathname of the new directory.
1180 """
1181 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1182 OPTIONS.tempfiles.append(dir_name)
1183 return dir_name
1184
1185
Doug Zongkereef39442009-04-02 12:14:19 -07001186def Cleanup():
1187 for i in OPTIONS.tempfiles:
1188 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001189 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001190 else:
1191 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001192 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001193
1194
1195class PasswordManager(object):
1196 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001197 self.editor = os.getenv("EDITOR")
1198 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001199
1200 def GetPasswords(self, items):
1201 """Get passwords corresponding to each string in 'items',
1202 returning a dict. (The dict may have keys in addition to the
1203 values in 'items'.)
1204
1205 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1206 user edit that file to add more needed passwords. If no editor is
1207 available, or $ANDROID_PW_FILE isn't define, prompts the user
1208 interactively in the ordinary way.
1209 """
1210
1211 current = self.ReadFile()
1212
1213 first = True
1214 while True:
1215 missing = []
1216 for i in items:
1217 if i not in current or not current[i]:
1218 missing.append(i)
1219 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001220 if not missing:
1221 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001222
1223 for i in missing:
1224 current[i] = ""
1225
1226 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001227 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001228 answer = raw_input("try to edit again? [y]> ").strip()
1229 if answer and answer[0] not in 'yY':
1230 raise RuntimeError("key passwords unavailable")
1231 first = False
1232
1233 current = self.UpdateAndReadFile(current)
1234
Dan Albert8b72aef2015-03-23 19:13:21 -07001235 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001236 """Prompt the user to enter a value (password) for each key in
1237 'current' whose value is fales. Returns a new dict with all the
1238 values.
1239 """
1240 result = {}
1241 for k, v in sorted(current.iteritems()):
1242 if v:
1243 result[k] = v
1244 else:
1245 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001246 result[k] = getpass.getpass(
1247 "Enter password for %s key> " % k).strip()
1248 if result[k]:
1249 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001250 return result
1251
1252 def UpdateAndReadFile(self, current):
1253 if not self.editor or not self.pwfile:
1254 return self.PromptResult(current)
1255
1256 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001257 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001258 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1259 f.write("# (Additional spaces are harmless.)\n\n")
1260
1261 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001262 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1263 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001264 f.write("[[[ %s ]]] %s\n" % (v, k))
1265 if not v and first_line is None:
1266 # position cursor on first line with no password.
1267 first_line = i + 4
1268 f.close()
1269
1270 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1271 _, _ = p.communicate()
1272
1273 return self.ReadFile()
1274
1275 def ReadFile(self):
1276 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001277 if self.pwfile is None:
1278 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001279 try:
1280 f = open(self.pwfile, "r")
1281 for line in f:
1282 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001283 if not line or line[0] == '#':
1284 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001285 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1286 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001287 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001288 else:
1289 result[m.group(2)] = m.group(1)
1290 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001291 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001292 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001293 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001294 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001295
1296
Dan Albert8e0178d2015-01-27 15:53:15 -08001297def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1298 compress_type=None):
1299 import datetime
1300
1301 # http://b/18015246
1302 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1303 # for files larger than 2GiB. We can work around this by adjusting their
1304 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1305 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1306 # it isn't clear to me exactly what circumstances cause this).
1307 # `zipfile.write()` must be used directly to work around this.
1308 #
1309 # This mess can be avoided if we port to python3.
1310 saved_zip64_limit = zipfile.ZIP64_LIMIT
1311 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1312
1313 if compress_type is None:
1314 compress_type = zip_file.compression
1315 if arcname is None:
1316 arcname = filename
1317
1318 saved_stat = os.stat(filename)
1319
1320 try:
1321 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1322 # file to be zipped and reset it when we're done.
1323 os.chmod(filename, perms)
1324
1325 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001326 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1327 # intentional. zip stores datetimes in local time without a time zone
1328 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1329 # in the zip archive.
1330 local_epoch = datetime.datetime.fromtimestamp(0)
1331 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001332 os.utime(filename, (timestamp, timestamp))
1333
1334 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1335 finally:
1336 os.chmod(filename, saved_stat.st_mode)
1337 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1338 zipfile.ZIP64_LIMIT = saved_zip64_limit
1339
1340
Tao Bao58c1b962015-05-20 09:32:18 -07001341def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001342 compress_type=None):
1343 """Wrap zipfile.writestr() function to work around the zip64 limit.
1344
1345 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1346 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1347 when calling crc32(bytes).
1348
1349 But it still works fine to write a shorter string into a large zip file.
1350 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1351 when we know the string won't be too long.
1352 """
1353
1354 saved_zip64_limit = zipfile.ZIP64_LIMIT
1355 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1356
1357 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1358 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001359 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001360 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001361 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001362 else:
Tao Baof3282b42015-04-01 11:21:55 -07001363 zinfo = zinfo_or_arcname
1364
1365 # If compress_type is given, it overrides the value in zinfo.
1366 if compress_type is not None:
1367 zinfo.compress_type = compress_type
1368
Tao Bao58c1b962015-05-20 09:32:18 -07001369 # If perms is given, it has a priority.
1370 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001371 # If perms doesn't set the file type, mark it as a regular file.
1372 if perms & 0o770000 == 0:
1373 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001374 zinfo.external_attr = perms << 16
1375
Tao Baof3282b42015-04-01 11:21:55 -07001376 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001377 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1378
Dan Albert8b72aef2015-03-23 19:13:21 -07001379 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001380 zipfile.ZIP64_LIMIT = saved_zip64_limit
1381
1382
Tao Bao89d7ab22017-12-14 17:05:33 -08001383def ZipDelete(zip_filename, entries):
1384 """Deletes entries from a ZIP file.
1385
1386 Since deleting entries from a ZIP file is not supported, it shells out to
1387 'zip -d'.
1388
1389 Args:
1390 zip_filename: The name of the ZIP file.
1391 entries: The name of the entry, or the list of names to be deleted.
1392
1393 Raises:
1394 AssertionError: In case of non-zero return from 'zip'.
1395 """
1396 if isinstance(entries, basestring):
1397 entries = [entries]
1398 cmd = ["zip", "-d", zip_filename] + entries
1399 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1400 stdoutdata, _ = proc.communicate()
1401 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1402 stdoutdata)
1403
1404
Tao Baof3282b42015-04-01 11:21:55 -07001405def ZipClose(zip_file):
1406 # http://b/18015246
1407 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1408 # central directory.
1409 saved_zip64_limit = zipfile.ZIP64_LIMIT
1410 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1411
1412 zip_file.close()
1413
1414 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001415
1416
1417class DeviceSpecificParams(object):
1418 module = None
1419 def __init__(self, **kwargs):
1420 """Keyword arguments to the constructor become attributes of this
1421 object, which is passed to all functions in the device-specific
1422 module."""
1423 for k, v in kwargs.iteritems():
1424 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001425 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001426
1427 if self.module is None:
1428 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001429 if not path:
1430 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001431 try:
1432 if os.path.isdir(path):
1433 info = imp.find_module("releasetools", [path])
1434 else:
1435 d, f = os.path.split(path)
1436 b, x = os.path.splitext(f)
1437 if x == ".py":
1438 f = b
1439 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001440 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001441 self.module = imp.load_module("device_specific", *info)
1442 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001443 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001444
1445 def _DoCall(self, function_name, *args, **kwargs):
1446 """Call the named function in the device-specific module, passing
1447 the given args and kwargs. The first argument to the call will be
1448 the DeviceSpecific object itself. If there is no module, or the
1449 module does not define the function, return the value of the
1450 'default' kwarg (which itself defaults to None)."""
1451 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001452 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001453 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1454
1455 def FullOTA_Assertions(self):
1456 """Called after emitting the block of assertions at the top of a
1457 full OTA package. Implementations can add whatever additional
1458 assertions they like."""
1459 return self._DoCall("FullOTA_Assertions")
1460
Doug Zongkere5ff5902012-01-17 10:55:37 -08001461 def FullOTA_InstallBegin(self):
1462 """Called at the start of full OTA installation."""
1463 return self._DoCall("FullOTA_InstallBegin")
1464
Doug Zongker05d3dea2009-06-22 11:32:31 -07001465 def FullOTA_InstallEnd(self):
1466 """Called at the end of full OTA installation; typically this is
1467 used to install the image for the device's baseband processor."""
1468 return self._DoCall("FullOTA_InstallEnd")
1469
1470 def IncrementalOTA_Assertions(self):
1471 """Called after emitting the block of assertions at the top of an
1472 incremental OTA package. Implementations can add whatever
1473 additional assertions they like."""
1474 return self._DoCall("IncrementalOTA_Assertions")
1475
Doug Zongkere5ff5902012-01-17 10:55:37 -08001476 def IncrementalOTA_VerifyBegin(self):
1477 """Called at the start of the verification phase of incremental
1478 OTA installation; additional checks can be placed here to abort
1479 the script before any changes are made."""
1480 return self._DoCall("IncrementalOTA_VerifyBegin")
1481
Doug Zongker05d3dea2009-06-22 11:32:31 -07001482 def IncrementalOTA_VerifyEnd(self):
1483 """Called at the end of the verification phase of incremental OTA
1484 installation; additional checks can be placed here to abort the
1485 script before any changes are made."""
1486 return self._DoCall("IncrementalOTA_VerifyEnd")
1487
Doug Zongkere5ff5902012-01-17 10:55:37 -08001488 def IncrementalOTA_InstallBegin(self):
1489 """Called at the start of incremental OTA installation (after
1490 verification is complete)."""
1491 return self._DoCall("IncrementalOTA_InstallBegin")
1492
Doug Zongker05d3dea2009-06-22 11:32:31 -07001493 def IncrementalOTA_InstallEnd(self):
1494 """Called at the end of incremental OTA installation; typically
1495 this is used to install the image for the device's baseband
1496 processor."""
1497 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001498
Tao Bao9bc6bb22015-11-09 16:58:28 -08001499 def VerifyOTA_Assertions(self):
1500 return self._DoCall("VerifyOTA_Assertions")
1501
Tao Bao76def242017-11-21 09:25:31 -08001502
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001503class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001504 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001505 self.name = name
1506 self.data = data
1507 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001508 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001509 self.sha1 = sha1(data).hexdigest()
1510
1511 @classmethod
1512 def FromLocalFile(cls, name, diskname):
1513 f = open(diskname, "rb")
1514 data = f.read()
1515 f.close()
1516 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001517
1518 def WriteToTemp(self):
1519 t = tempfile.NamedTemporaryFile()
1520 t.write(self.data)
1521 t.flush()
1522 return t
1523
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001524 def WriteToDir(self, d):
1525 with open(os.path.join(d, self.name), "wb") as fp:
1526 fp.write(self.data)
1527
Geremy Condra36bd3652014-02-06 19:45:10 -08001528 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001529 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001530
Tao Bao76def242017-11-21 09:25:31 -08001531
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001532DIFF_PROGRAM_BY_EXT = {
1533 ".gz" : "imgdiff",
1534 ".zip" : ["imgdiff", "-z"],
1535 ".jar" : ["imgdiff", "-z"],
1536 ".apk" : ["imgdiff", "-z"],
1537 ".img" : "imgdiff",
1538 }
1539
Tao Bao76def242017-11-21 09:25:31 -08001540
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001541class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001542 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001543 self.tf = tf
1544 self.sf = sf
1545 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001546 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001547
1548 def ComputePatch(self):
1549 """Compute the patch (as a string of data) needed to turn sf into
1550 tf. Returns the same tuple as GetPatch()."""
1551
1552 tf = self.tf
1553 sf = self.sf
1554
Doug Zongker24cd2802012-08-14 16:36:15 -07001555 if self.diff_program:
1556 diff_program = self.diff_program
1557 else:
1558 ext = os.path.splitext(tf.name)[1]
1559 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001560
1561 ttemp = tf.WriteToTemp()
1562 stemp = sf.WriteToTemp()
1563
1564 ext = os.path.splitext(tf.name)[1]
1565
1566 try:
1567 ptemp = tempfile.NamedTemporaryFile()
1568 if isinstance(diff_program, list):
1569 cmd = copy.copy(diff_program)
1570 else:
1571 cmd = [diff_program]
1572 cmd.append(stemp.name)
1573 cmd.append(ttemp.name)
1574 cmd.append(ptemp.name)
1575 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001576 err = []
1577 def run():
1578 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001579 if e:
1580 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001581 th = threading.Thread(target=run)
1582 th.start()
1583 th.join(timeout=300) # 5 mins
1584 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001585 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001586 p.terminate()
1587 th.join(5)
1588 if th.is_alive():
1589 p.kill()
1590 th.join()
1591
Tianjie Xua2a9f992018-01-05 15:15:54 -08001592 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001593 print("WARNING: failure running %s:\n%s\n" % (
1594 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001595 self.patch = None
1596 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001597 diff = ptemp.read()
1598 finally:
1599 ptemp.close()
1600 stemp.close()
1601 ttemp.close()
1602
1603 self.patch = diff
1604 return self.tf, self.sf, self.patch
1605
1606
1607 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001608 """Returns a tuple of (target_file, source_file, patch_data).
1609
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001610 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001611 computing the patch failed.
1612 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001613 return self.tf, self.sf, self.patch
1614
1615
1616def ComputeDifferences(diffs):
1617 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001618 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001619
1620 # Do the largest files first, to try and reduce the long-pole effect.
1621 by_size = [(i.tf.size, i) for i in diffs]
1622 by_size.sort(reverse=True)
1623 by_size = [i[1] for i in by_size]
1624
1625 lock = threading.Lock()
1626 diff_iter = iter(by_size) # accessed under lock
1627
1628 def worker():
1629 try:
1630 lock.acquire()
1631 for d in diff_iter:
1632 lock.release()
1633 start = time.time()
1634 d.ComputePatch()
1635 dur = time.time() - start
1636 lock.acquire()
1637
1638 tf, sf, patch = d.GetPatch()
1639 if sf.name == tf.name:
1640 name = tf.name
1641 else:
1642 name = "%s (%s)" % (tf.name, sf.name)
1643 if patch is None:
Tao Bao76def242017-11-21 09:25:31 -08001644 print(
1645 "patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001646 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001647 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1648 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001649 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001650 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001651 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001652 raise
1653
1654 # start worker threads; wait for them all to finish.
1655 threads = [threading.Thread(target=worker)
1656 for i in range(OPTIONS.worker_threads)]
1657 for th in threads:
1658 th.start()
1659 while threads:
1660 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001661
1662
Dan Albert8b72aef2015-03-23 19:13:21 -07001663class BlockDifference(object):
1664 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001665 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001666 self.tgt = tgt
1667 self.src = src
1668 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001669 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001670 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001671
Tao Baodd2a5892015-03-12 12:32:37 -07001672 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001673 version = max(
1674 int(i) for i in
1675 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001676 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001677 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001678
1679 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001680 version=self.version,
1681 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001682 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001683 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001684 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001685 self.touched_src_ranges = b.touched_src_ranges
1686 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001687
Tao Baoaac4ad52015-10-16 15:26:34 -07001688 if src is None:
1689 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1690 else:
1691 _, self.device = GetTypeAndDevice("/" + partition,
1692 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001693
Tao Baod8d14be2016-02-04 14:26:02 -08001694 @property
1695 def required_cache(self):
1696 return self._required_cache
1697
Tao Bao76def242017-11-21 09:25:31 -08001698 def WriteScript(self, script, output_zip, progress=None,
1699 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001700 if not self.src:
1701 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001702 script.Print("Patching %s image unconditionally..." % (self.partition,))
1703 else:
1704 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001705
Dan Albert8b72aef2015-03-23 19:13:21 -07001706 if progress:
1707 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001708 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001709
1710 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001711 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001712
Tao Bao9bc6bb22015-11-09 16:58:28 -08001713 def WriteStrictVerifyScript(self, script):
1714 """Verify all the blocks in the care_map, including clobbered blocks.
1715
1716 This differs from the WriteVerifyScript() function: a) it prints different
1717 error messages; b) it doesn't allow half-way updated images to pass the
1718 verification."""
1719
1720 partition = self.partition
1721 script.Print("Verifying %s..." % (partition,))
1722 ranges = self.tgt.care_map
1723 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001724 script.AppendExtra(
1725 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1726 'ui_print("\\"%s\\" has unexpected contents.");' % (
1727 self.device, ranges_str,
1728 self.tgt.TotalSha1(include_clobbered_blocks=True),
1729 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001730 script.AppendExtra("")
1731
Tao Baod522bdc2016-04-12 15:53:16 -07001732 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001733 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001734
1735 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001736 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001737 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001738
1739 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001740 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001741 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001742 ranges = self.touched_src_ranges
1743 expected_sha1 = self.touched_src_sha1
1744 else:
1745 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1746 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001747
1748 # No blocks to be checked, skipping.
1749 if not ranges:
1750 return
1751
Tao Bao5ece99d2015-05-12 11:42:31 -07001752 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001753 script.AppendExtra(
1754 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1755 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1756 '"%s.patch.dat")) then' % (
1757 self.device, ranges_str, expected_sha1,
1758 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001759 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001760 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001761
Tianjie Xufc3422a2015-12-15 11:53:59 -08001762 if self.version >= 4:
1763
1764 # Bug: 21124327
1765 # When generating incrementals for the system and vendor partitions in
1766 # version 4 or newer, explicitly check the first block (which contains
1767 # the superblock) of the partition to see if it's what we expect. If
1768 # this check fails, give an explicit log message about the partition
1769 # having been remounted R/W (the most likely explanation).
1770 if self.check_first_block:
1771 script.AppendExtra('check_first_block("%s");' % (self.device,))
1772
1773 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001774 if partition == "system":
1775 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1776 else:
1777 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001778 script.AppendExtra((
1779 'ifelse (block_image_recover("{device}", "{ranges}") && '
1780 'block_image_verify("{device}", '
1781 'package_extract_file("{partition}.transfer.list"), '
1782 '"{partition}.new.dat", "{partition}.patch.dat"), '
1783 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001784 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001785 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001786 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001787
Tao Baodd2a5892015-03-12 12:32:37 -07001788 # Abort the OTA update. Note that the incremental OTA cannot be applied
1789 # even if it may match the checksum of the target partition.
1790 # a) If version < 3, operations like move and erase will make changes
1791 # unconditionally and damage the partition.
1792 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001793 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001794 if partition == "system":
1795 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1796 else:
1797 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1798 script.AppendExtra((
1799 'abort("E%d: %s partition has unexpected contents");\n'
1800 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001801
Tao Bao5fcaaef2015-06-01 13:40:49 -07001802 def _WritePostInstallVerifyScript(self, script):
1803 partition = self.partition
1804 script.Print('Verifying the updated %s image...' % (partition,))
1805 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1806 ranges = self.tgt.care_map
1807 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001808 script.AppendExtra(
1809 'if range_sha1("%s", "%s") == "%s" then' % (
1810 self.device, ranges_str,
1811 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001812
1813 # Bug: 20881595
1814 # Verify that extended blocks are really zeroed out.
1815 if self.tgt.extended:
1816 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001817 script.AppendExtra(
1818 'if range_sha1("%s", "%s") == "%s" then' % (
1819 self.device, ranges_str,
1820 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001821 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001822 if partition == "system":
1823 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1824 else:
1825 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001826 script.AppendExtra(
1827 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001828 ' abort("E%d: %s partition has unexpected non-zero contents after '
1829 'OTA update");\n'
1830 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001831 else:
1832 script.Print('Verified the updated %s image.' % (partition,))
1833
Tianjie Xu209db462016-05-24 17:34:52 -07001834 if partition == "system":
1835 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1836 else:
1837 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1838
Tao Bao5fcaaef2015-06-01 13:40:49 -07001839 script.AppendExtra(
1840 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001841 ' abort("E%d: %s partition has unexpected contents after OTA '
1842 'update");\n'
1843 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001844
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001845 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001846 ZipWrite(output_zip,
1847 '{}.transfer.list'.format(self.path),
1848 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001849
Tao Bao76def242017-11-21 09:25:31 -08001850 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1851 # its size. Quailty 9 almost triples the compression time but doesn't
1852 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001853 # zip | brotli(quality 6) | brotli(quality 9)
1854 # compressed_size: 942M | 869M (~8% reduced) | 854M
1855 # compression_time: 75s | 265s | 719s
1856 # decompression_time: 15s | 25s | 25s
1857
1858 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001859 brotli_cmd = ['brotli', '--quality=6',
1860 '--output={}.new.dat.br'.format(self.path),
1861 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001862 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao80921982018-03-21 21:02:19 -07001863 p = Run(brotli_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1864 stdoutdata, _ = p.communicate()
1865 assert p.returncode == 0, \
1866 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1867 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001868
1869 new_data_name = '{}.new.dat.br'.format(self.partition)
1870 ZipWrite(output_zip,
1871 '{}.new.dat.br'.format(self.path),
1872 new_data_name,
1873 compress_type=zipfile.ZIP_STORED)
1874 else:
1875 new_data_name = '{}.new.dat'.format(self.partition)
1876 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1877
Dan Albert8e0178d2015-01-27 15:53:15 -08001878 ZipWrite(output_zip,
1879 '{}.patch.dat'.format(self.path),
1880 '{}.patch.dat'.format(self.partition),
1881 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001882
Tianjie Xu209db462016-05-24 17:34:52 -07001883 if self.partition == "system":
1884 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1885 else:
1886 code = ErrorCode.VENDOR_UPDATE_FAILURE
1887
Dan Albert8e0178d2015-01-27 15:53:15 -08001888 call = ('block_image_update("{device}", '
1889 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001890 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001891 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001892 device=self.device, partition=self.partition,
1893 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001894 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001895
Dan Albert8b72aef2015-03-23 19:13:21 -07001896 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001897 data = source.ReadRangeSet(ranges)
1898 ctx = sha1()
1899
1900 for p in data:
1901 ctx.update(p)
1902
1903 return ctx.hexdigest()
1904
Tao Baoe9b61912015-07-09 17:37:49 -07001905 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1906 """Return the hash value for all zero blocks."""
1907 zero_block = '\x00' * 4096
1908 ctx = sha1()
1909 for _ in range(num_blocks):
1910 ctx.update(zero_block)
1911
1912 return ctx.hexdigest()
1913
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001914
1915DataImage = blockimgdiff.DataImage
1916
Tao Bao76def242017-11-21 09:25:31 -08001917
Doug Zongker96a57e72010-09-26 14:57:41 -07001918# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001919PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001920 "ext4": "EMMC",
1921 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001922 "f2fs": "EMMC",
1923 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001924}
Doug Zongker96a57e72010-09-26 14:57:41 -07001925
Tao Bao76def242017-11-21 09:25:31 -08001926
Doug Zongker96a57e72010-09-26 14:57:41 -07001927def GetTypeAndDevice(mount_point, info):
1928 fstab = info["fstab"]
1929 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001930 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1931 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001932 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001933 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001934
1935
1936def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001937 """Parses and converts a PEM-encoded certificate into DER-encoded.
1938
1939 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1940
1941 Returns:
1942 The decoded certificate string.
1943 """
1944 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001945 save = False
1946 for line in data.split("\n"):
1947 if "--END CERTIFICATE--" in line:
1948 break
1949 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001950 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001951 if "--BEGIN CERTIFICATE--" in line:
1952 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001953 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001954 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001955
Tao Bao04e1f012018-02-04 12:13:35 -08001956
1957def ExtractPublicKey(cert):
1958 """Extracts the public key (PEM-encoded) from the given certificate file.
1959
1960 Args:
1961 cert: The certificate filename.
1962
1963 Returns:
1964 The public key string.
1965
1966 Raises:
1967 AssertionError: On non-zero return from 'openssl'.
1968 """
1969 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1970 # While openssl 1.1 writes the key into the given filename followed by '-out',
1971 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1972 # stdout instead.
1973 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1974 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1975 pubkey, stderrdata = proc.communicate()
1976 assert proc.returncode == 0, \
1977 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1978 return pubkey
1979
1980
Doug Zongker412c02f2014-02-13 10:58:24 -08001981def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1982 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001983 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001984
Tao Bao6d5d6232018-03-09 17:04:42 -08001985 Most of the space in the boot and recovery images is just the kernel, which is
1986 identical for the two, so the resulting patch should be efficient. Add it to
1987 the output zip, along with a shell script that is run from init.rc on first
1988 boot to actually do the patching and install the new recovery image.
1989
1990 Args:
1991 input_dir: The top-level input directory of the target-files.zip.
1992 output_sink: The callback function that writes the result.
1993 recovery_img: File object for the recovery image.
1994 boot_img: File objects for the boot image.
1995 info_dict: A dict returned by common.LoadInfoDict() on the input
1996 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001997 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001998 if info_dict is None:
1999 info_dict = OPTIONS.info_dict
2000
Tao Bao6d5d6232018-03-09 17:04:42 -08002001 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002002
Tao Baof2cffbd2015-07-22 12:33:18 -07002003 if full_recovery_image:
2004 output_sink("etc/recovery.img", recovery_img.data)
2005
2006 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002007 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002008 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002009 # With system-root-image, boot and recovery images will have mismatching
2010 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2011 # to handle such a case.
2012 if system_root_image:
2013 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002014 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002015 assert not os.path.exists(path)
2016 else:
2017 diff_program = ["imgdiff"]
2018 if os.path.exists(path):
2019 diff_program.append("-b")
2020 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002021 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002022 else:
2023 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002024
2025 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2026 _, _, patch = d.ComputePatch()
2027 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002028
Dan Albertebb19aa2015-03-27 19:11:53 -07002029 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002030 # The following GetTypeAndDevice()s need to use the path in the target
2031 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002032 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2033 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2034 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002035 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002036
Tao Baof2cffbd2015-07-22 12:33:18 -07002037 if full_recovery_image:
2038 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002039if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2040 applypatch \\
2041 --flash /system/etc/recovery.img \\
2042 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2043 log -t recovery "Installing new recovery image: succeeded" || \\
2044 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002045else
2046 log -t recovery "Recovery image already installed"
2047fi
2048""" % {'type': recovery_type,
2049 'device': recovery_device,
2050 'sha1': recovery_img.sha1,
2051 'size': recovery_img.size}
2052 else:
2053 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002054if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2055 applypatch %(bonus_args)s \\
2056 --patch /system/recovery-from-boot.p \\
2057 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2058 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2059 log -t recovery "Installing new recovery image: succeeded" || \\
2060 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002061else
2062 log -t recovery "Recovery image already installed"
2063fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002064""" % {'boot_size': boot_img.size,
2065 'boot_sha1': boot_img.sha1,
2066 'recovery_size': recovery_img.size,
2067 'recovery_sha1': recovery_img.sha1,
2068 'boot_type': boot_type,
2069 'boot_device': boot_device,
2070 'recovery_type': recovery_type,
2071 'recovery_device': recovery_device,
2072 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002073
2074 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002075 # in the L release.
2076 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002077
Tao Bao89fbb0f2017-01-10 10:47:58 -08002078 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002079
2080 output_sink(sh_location, sh)