blob: caefb79168709d3b068c7eb985f180ae53be1441 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Doug Zongkerea5d7a92010-09-12 15:26:16 -070017import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070018import errno
Doug Zongkereef39442009-04-02 12:14:19 -070019import getopt
20import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010021import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070022import imp
Doug Zongkereef39442009-04-02 12:14:19 -070023import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080024import platform
Doug Zongkereef39442009-04-02 12:14:19 -070025import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070026import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070027import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080028import string
Doug Zongkereef39442009-04-02 12:14:19 -070029import subprocess
30import sys
31import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070032import threading
33import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070034import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080035from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070036
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070037import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080038import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070039
Dan Albert8b72aef2015-03-23 19:13:21 -070040class Options(object):
41 def __init__(self):
42 platform_search_path = {
43 "linux2": "out/host/linux-x86",
44 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070045 }
Doug Zongker85448772014-09-09 14:59:20 -070046
Tao Bao76def242017-11-21 09:25:31 -080047 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080049 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070050 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080052 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070053 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070055 # use otatools built boot_signer by default
56 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070057 self.boot_signer_args = []
58 self.verity_signer_path = None
59 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.verbose = False
61 self.tempfiles = []
62 self.device_specific = None
63 self.extras = {}
64 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070065 self.source_info_dict = None
66 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070068 # Stash size cannot exceed cache_size * threshold.
69 self.cache_size = None
70 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070071
72
73OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070074
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080075
76# Values for "certificate" in apkcerts that mean special things.
77SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
78
Tao Bao9dd909e2017-11-14 11:27:32 -080079
80# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010081AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010082 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080083
84
Tianjie Xu209db462016-05-24 17:34:52 -070085class ErrorCode(object):
86 """Define error_codes for failures that happen during the actual
87 update package installation.
88
89 Error codes 0-999 are reserved for failures before the package
90 installation (i.e. low battery, package verification failure).
91 Detailed code in 'bootable/recovery/error_code.h' """
92
93 SYSTEM_VERIFICATION_FAILURE = 1000
94 SYSTEM_UPDATE_FAILURE = 1001
95 SYSTEM_UNEXPECTED_CONTENTS = 1002
96 SYSTEM_NONZERO_CONTENTS = 1003
97 SYSTEM_RECOVER_FAILURE = 1004
98 VENDOR_VERIFICATION_FAILURE = 2000
99 VENDOR_UPDATE_FAILURE = 2001
100 VENDOR_UNEXPECTED_CONTENTS = 2002
101 VENDOR_NONZERO_CONTENTS = 2003
102 VENDOR_RECOVER_FAILURE = 2004
103 OEM_PROP_MISMATCH = 3000
104 FINGERPRINT_MISMATCH = 3001
105 THUMBPRINT_MISMATCH = 3002
106 OLDER_BUILD = 3003
107 DEVICE_MISMATCH = 3004
108 BAD_PATCH_FILE = 3005
109 INSUFFICIENT_CACHE_SPACE = 3006
110 TUNE_PARTITION_FAILURE = 3007
111 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800112
Tao Bao80921982018-03-21 21:02:19 -0700113
Dan Albert8b72aef2015-03-23 19:13:21 -0700114class ExternalError(RuntimeError):
115 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700116
117
Tao Bao39451582017-05-04 11:10:47 -0700118def Run(args, verbose=None, **kwargs):
119 """Create and return a subprocess.Popen object.
120
121 Caller can specify if the command line should be printed. The global
122 OPTIONS.verbose will be used if not specified.
123 """
124 if verbose is None:
125 verbose = OPTIONS.verbose
126 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800127 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700128 return subprocess.Popen(args, **kwargs)
129
130
Tao Baoc765cca2018-01-31 17:32:40 -0800131def RoundUpTo4K(value):
132 rounded_up = value + 4095
133 return rounded_up - (rounded_up % 4096)
134
135
Ying Wang7e6d4e42010-12-13 16:25:36 -0800136def CloseInheritedPipes():
137 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
138 before doing other work."""
139 if platform.system() != "Darwin":
140 return
141 for d in range(3, 1025):
142 try:
143 stat = os.fstat(d)
144 if stat is not None:
145 pipebit = stat[0] & 0x1000
146 if pipebit != 0:
147 os.close(d)
148 except OSError:
149 pass
150
151
Tao Bao2c15d9e2015-07-09 11:51:16 -0700152def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700153 """Read and parse the META/misc_info.txt key/value pairs from the
154 input target files and return a dict."""
155
Doug Zongkerc9253822014-02-04 12:17:58 -0800156 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700157 if isinstance(input_file, zipfile.ZipFile):
158 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800159 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700160 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800161 try:
162 with open(path) as f:
163 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700164 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800165 if e.errno == errno.ENOENT:
166 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800167
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700168 try:
Michael Runge6e836112014-04-15 17:40:21 -0700169 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700170 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800171 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700172
Tao Bao6cd54732017-02-27 15:12:05 -0800173 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800174 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800175
Tao Bao84e75682015-07-19 02:38:53 -0700176 # A few properties are stored as links to the files in the out/ directory.
177 # It works fine with the build system. However, they are no longer available
178 # when (re)generating from target_files zip. If input_dir is not None, we
179 # are doing repacking. Redirect those properties to the actual files in the
180 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700181 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400182 # We carry a copy of file_contexts.bin under META/. If not available,
183 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tom Cherryd14b8952018-08-09 14:26:00 -0700184 # to build images than the one running on device, in that case, we must
185 # have the one for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700186 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
187 fc_config = os.path.join(input_dir, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700188 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700189
Tom Cherryd14b8952018-08-09 14:26:00 -0700190 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700191
Tom Cherryd14b8952018-08-09 14:26:00 -0700192 # Similarly we need to redirect "root_dir", and "root_fs_config".
193 d["root_dir"] = os.path.join(input_dir, "ROOT")
194 d["root_fs_config"] = os.path.join(
195 input_dir, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700196
Tao Baof54216f2016-03-29 15:12:37 -0700197 # Redirect {system,vendor}_base_fs_file.
198 if "system_base_fs_file" in d:
199 basename = os.path.basename(d["system_base_fs_file"])
200 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700201 if os.path.exists(system_base_fs_file):
202 d["system_base_fs_file"] = system_base_fs_file
203 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800204 print("Warning: failed to find system base fs file: %s" % (
205 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700206 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700207
208 if "vendor_base_fs_file" in d:
209 basename = os.path.basename(d["vendor_base_fs_file"])
210 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700211 if os.path.exists(vendor_base_fs_file):
212 d["vendor_base_fs_file"] = vendor_base_fs_file
213 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800214 print("Warning: failed to find vendor base fs file: %s" % (
215 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700216 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700217
Doug Zongker37974732010-09-16 17:44:38 -0700218 def makeint(key):
219 if key in d:
220 d[key] = int(d[key], 0)
221
222 makeint("recovery_api_version")
223 makeint("blocksize")
224 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700225 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700226 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700227 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700228 makeint("recovery_size")
229 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800230 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700231
Tao Bao76def242017-11-21 09:25:31 -0800232 system_root_image = d.get("system_root_image") == "true"
233 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700234 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700235 if isinstance(input_file, zipfile.ZipFile):
236 if recovery_fstab_path not in input_file.namelist():
237 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
238 else:
239 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
240 if not os.path.exists(path):
241 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800242 d["fstab"] = LoadRecoveryFSTab(
243 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700244
Tao Bao76def242017-11-21 09:25:31 -0800245 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700246 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700247 if isinstance(input_file, zipfile.ZipFile):
248 if recovery_fstab_path not in input_file.namelist():
249 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
250 else:
251 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
252 if not os.path.exists(path):
253 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800254 d["fstab"] = LoadRecoveryFSTab(
255 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700256
Tianjie Xucfa86222016-03-07 16:31:19 -0800257 else:
258 d["fstab"] = None
259
Tao Baobcd1d162017-08-26 13:10:26 -0700260 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
261 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Tao Bao12d87fc2018-01-31 12:18:52 -0800262
263 # Set up the salt (based on fingerprint or thumbprint) that will be used when
264 # adding AVB footer.
265 if d.get("avb_enable") == "true":
266 fp = None
267 if "build.prop" in d:
268 build_prop = d["build.prop"]
269 if "ro.build.fingerprint" in build_prop:
270 fp = build_prop["ro.build.fingerprint"]
271 elif "ro.build.thumbprint" in build_prop:
272 fp = build_prop["ro.build.thumbprint"]
273 if fp:
274 d["avb_salt"] = sha256(fp).hexdigest()
275
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700276 return d
277
Tao Baod1de6f32017-03-01 16:38:48 -0800278
Tao Baobcd1d162017-08-26 13:10:26 -0700279def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700280 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700281 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700282 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700283 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700284 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700285 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700286
Tao Baod1de6f32017-03-01 16:38:48 -0800287
Michael Runge6e836112014-04-15 17:40:21 -0700288def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700289 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700290 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700291 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700292 if not line or line.startswith("#"):
293 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700294 if "=" in line:
295 name, value = line.split("=", 1)
296 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700297 return d
298
Tao Baod1de6f32017-03-01 16:38:48 -0800299
Tianjie Xucfa86222016-03-07 16:31:19 -0800300def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
301 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700302 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800303 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700304 self.mount_point = mount_point
305 self.fs_type = fs_type
306 self.device = device
307 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700308 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700309
310 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800311 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700312 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800313 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700314 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700315
Tao Baod1de6f32017-03-01 16:38:48 -0800316 assert fstab_version == 2
317
318 d = {}
319 for line in data.split("\n"):
320 line = line.strip()
321 if not line or line.startswith("#"):
322 continue
323
324 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
325 pieces = line.split()
326 if len(pieces) != 5:
327 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
328
329 # Ignore entries that are managed by vold.
330 options = pieces[4]
331 if "voldmanaged=" in options:
332 continue
333
334 # It's a good line, parse it.
335 length = 0
336 options = options.split(",")
337 for i in options:
338 if i.startswith("length="):
339 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800340 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800341 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700342 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800343
Tao Baod1de6f32017-03-01 16:38:48 -0800344 mount_flags = pieces[3]
345 # Honor the SELinux context if present.
346 context = None
347 for i in mount_flags.split(","):
348 if i.startswith("context="):
349 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800350
Tao Baod1de6f32017-03-01 16:38:48 -0800351 mount_point = pieces[1]
352 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
353 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800354
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700355 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700356 # system. Other areas assume system is always at "/system" so point /system
357 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700358 if system_root_image:
359 assert not d.has_key("/system") and d.has_key("/")
360 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700361 return d
362
363
Doug Zongker37974732010-09-16 17:44:38 -0700364def DumpInfoDict(d):
365 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800366 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700367
Dan Albert8b72aef2015-03-23 19:13:21 -0700368
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800369def AppendAVBSigningArgs(cmd, partition):
370 """Append signing arguments for avbtool."""
371 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
372 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
373 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
374 if key_path and algorithm:
375 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700376 avb_salt = OPTIONS.info_dict.get("avb_salt")
377 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
378 if avb_salt and partition != "vbmeta":
379 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800380
381
Tao Bao02a08592018-07-22 12:40:45 -0700382def GetAvbChainedPartitionArg(partition, info_dict, key=None):
383 """Constructs and returns the arg to build or verify a chained partition.
384
385 Args:
386 partition: The partition name.
387 info_dict: The info dict to look up the key info and rollback index
388 location.
389 key: The key to be used for building or verifying the partition. Defaults to
390 the key listed in info_dict.
391
392 Returns:
393 A string of form "partition:rollback_index_location:key" that can be used to
394 build or verify vbmeta image.
395
396 Raises:
397 AssertionError: When it fails to extract the public key with avbtool.
398 """
399 if key is None:
400 key = info_dict["avb_" + partition + "_key_path"]
401 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
402 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
403 proc = Run(
404 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path],
405 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
406 stdoutdata, _ = proc.communicate()
407 assert proc.returncode == 0, \
408 "Failed to extract pubkey for {}:\n{}".format(
409 partition, stdoutdata)
410
411 rollback_index_location = info_dict[
412 "avb_" + partition + "_rollback_index_location"]
413 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
414
415
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700416def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800417 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700418 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700419
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700420 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800421 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
422 we are building a two-step special image (i.e. building a recovery image to
423 be loaded into /boot in two-step OTAs).
424
425 Return the image data, or None if sourcedir does not appear to contains files
426 for building the requested image.
427 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700428
429 def make_ramdisk():
430 ramdisk_img = tempfile.NamedTemporaryFile()
431
432 if os.access(fs_config_file, os.F_OK):
433 cmd = ["mkbootfs", "-f", fs_config_file,
434 os.path.join(sourcedir, "RAMDISK")]
435 else:
436 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
437 p1 = Run(cmd, stdout=subprocess.PIPE)
438 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
439
440 p2.wait()
441 p1.wait()
442 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
443 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
444
445 return ramdisk_img
446
447 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
448 return None
449
450 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700451 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700452
Doug Zongkerd5131602012-08-02 14:46:42 -0700453 if info_dict is None:
454 info_dict = OPTIONS.info_dict
455
Doug Zongkereef39442009-04-02 12:14:19 -0700456 img = tempfile.NamedTemporaryFile()
457
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700458 if has_ramdisk:
459 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700460
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800461 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
462 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
463
464 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700465
Benoit Fradina45a8682014-07-14 21:00:43 +0200466 fn = os.path.join(sourcedir, "second")
467 if os.access(fn, os.F_OK):
468 cmd.append("--second")
469 cmd.append(fn)
470
Doug Zongker171f1cd2009-06-15 22:36:37 -0700471 fn = os.path.join(sourcedir, "cmdline")
472 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700473 cmd.append("--cmdline")
474 cmd.append(open(fn).read().rstrip("\n"))
475
476 fn = os.path.join(sourcedir, "base")
477 if os.access(fn, os.F_OK):
478 cmd.append("--base")
479 cmd.append(open(fn).read().rstrip("\n"))
480
Ying Wang4de6b5b2010-08-25 14:29:34 -0700481 fn = os.path.join(sourcedir, "pagesize")
482 if os.access(fn, os.F_OK):
483 cmd.append("--pagesize")
484 cmd.append(open(fn).read().rstrip("\n"))
485
Tao Bao76def242017-11-21 09:25:31 -0800486 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700487 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700488 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700489
Tao Bao76def242017-11-21 09:25:31 -0800490 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000491 if args and args.strip():
492 cmd.extend(shlex.split(args))
493
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700494 if has_ramdisk:
495 cmd.extend(["--ramdisk", ramdisk_img.name])
496
Tao Baod95e9fd2015-03-29 23:07:41 -0700497 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800498 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700499 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700500 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700501 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700502 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700503
Tao Baobf70c312017-07-11 17:27:55 -0700504 # "boot" or "recovery", without extension.
505 partition_name = os.path.basename(sourcedir).lower()
506
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700507 if (partition_name == "recovery" and
508 info_dict.get("include_recovery_dtbo") == "true"):
509 fn = os.path.join(sourcedir, "recovery_dtbo")
510 cmd.extend(["--recovery_dtbo", fn])
511
Doug Zongker38a649f2009-06-17 09:07:09 -0700512 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700513 p.communicate()
Tao Baobf70c312017-07-11 17:27:55 -0700514 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700515
Tao Bao76def242017-11-21 09:25:31 -0800516 if (info_dict.get("boot_signer") == "true" and
517 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800518 # Hard-code the path as "/boot" for two-step special recovery image (which
519 # will be loaded into /boot during the two-step OTA).
520 if two_step_image:
521 path = "/boot"
522 else:
Tao Baobf70c312017-07-11 17:27:55 -0700523 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700524 cmd = [OPTIONS.boot_signer_path]
525 cmd.extend(OPTIONS.boot_signer_args)
526 cmd.extend([path, img.name,
527 info_dict["verity_key"] + ".pk8",
528 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700529 p = Run(cmd, stdout=subprocess.PIPE)
530 p.communicate()
531 assert p.returncode == 0, "boot_signer of %s image failed" % path
532
Tao Baod95e9fd2015-03-29 23:07:41 -0700533 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800534 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700535 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700536 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800537 # We have switched from the prebuilt futility binary to using the tool
538 # (futility-host) built from the source. Override the setting in the old
539 # TF.zip.
540 futility = info_dict["futility"]
541 if futility.startswith("prebuilts/"):
542 futility = "futility-host"
543 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700544 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700545 info_dict["vboot_key"] + ".vbprivk",
546 info_dict["vboot_subkey"] + ".vbprivk",
547 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700548 img.name]
549 p = Run(cmd, stdout=subprocess.PIPE)
550 p.communicate()
551 assert p.returncode == 0, "vboot_signer of %s image failed" % path
552
Tao Baof3282b42015-04-01 11:21:55 -0700553 # Clean up the temp files.
554 img_unsigned.close()
555 img_keyblock.close()
556
David Zeuthen8fecb282017-12-01 16:24:01 -0500557 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800558 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700559 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500560 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400561 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700562 "--partition_size", str(part_size), "--partition_name",
563 partition_name]
564 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500565 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400566 if args and args.strip():
567 cmd.extend(shlex.split(args))
568 p = Run(cmd, stdout=subprocess.PIPE)
569 p.communicate()
570 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c312017-07-11 17:27:55 -0700571 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500572
573 img.seek(os.SEEK_SET, 0)
574 data = img.read()
575
576 if has_ramdisk:
577 ramdisk_img.close()
578 img.close()
579
580 return data
581
582
Doug Zongkerd5131602012-08-02 14:46:42 -0700583def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800584 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700585 """Return a File object with the desired bootable image.
586
587 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
588 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
589 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700590
Doug Zongker55d93282011-01-25 17:03:34 -0800591 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
592 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800593 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800594 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700595
596 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
597 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800598 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700599 return File.FromLocalFile(name, prebuilt_path)
600
Tao Bao89fbb0f2017-01-10 10:47:58 -0800601 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700602
603 if info_dict is None:
604 info_dict = OPTIONS.info_dict
605
606 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800607 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
608 # for recovery.
609 has_ramdisk = (info_dict.get("system_root_image") != "true" or
610 prebuilt_name != "boot.img" or
611 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700612
Doug Zongker6f1d0312014-08-22 08:07:12 -0700613 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400614 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
615 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800616 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700617 if data:
618 return File(name, data)
619 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800620
Doug Zongkereef39442009-04-02 12:14:19 -0700621
Narayan Kamatha07bf042017-08-14 14:49:21 +0100622def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800623 """Gunzips the given gzip compressed file to a given output file."""
624 with gzip.open(in_filename, "rb") as in_file, \
625 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100626 shutil.copyfileobj(in_file, out_file)
627
628
Doug Zongker75f17362009-12-08 13:46:44 -0800629def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800630 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800631
Tao Bao1c830bf2017-12-25 10:43:47 -0800632 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
633 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800634
Tao Bao1c830bf2017-12-25 10:43:47 -0800635 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800636 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800637 """
Doug Zongkereef39442009-04-02 12:14:19 -0700638
Doug Zongker55d93282011-01-25 17:03:34 -0800639 def unzip_to_dir(filename, dirname):
640 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
641 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800642 cmd.extend(pattern)
Tao Bao80921982018-03-21 21:02:19 -0700643 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
644 stdoutdata, _ = p.communicate()
Doug Zongker55d93282011-01-25 17:03:34 -0800645 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700646 raise ExternalError(
647 "Failed to unzip input target-files \"{}\":\n{}".format(
648 filename, stdoutdata))
Doug Zongker55d93282011-01-25 17:03:34 -0800649
Tao Bao1c830bf2017-12-25 10:43:47 -0800650 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800651 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
652 if m:
653 unzip_to_dir(m.group(1), tmp)
654 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
655 filename = m.group(1)
656 else:
657 unzip_to_dir(filename, tmp)
658
Tao Baodba59ee2018-01-09 13:21:02 -0800659 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700660
661
Tao Baoe709b092018-02-07 12:40:00 -0800662def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -0800663 """Returns a SparseImage object suitable for passing to BlockImageDiff.
664
665 This function loads the specified sparse image from the given path, and
666 performs additional processing for OTA purpose. For example, it always adds
667 block 0 to clobbered blocks list. It also detects files that cannot be
668 reconstructed from the block list, for whom we should avoid applying imgdiff.
669
670 Args:
671 which: The partition name, which must be "system" or "vendor".
672 tmpdir: The directory that contains the prebuilt image and block map file.
673 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800674 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -0800675
676 Returns:
677 A SparseImage object, with file_map info loaded.
678 """
679 assert which in ("system", "vendor")
680
681 path = os.path.join(tmpdir, "IMAGES", which + ".img")
682 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
683
684 # The image and map files must have been created prior to calling
685 # ota_from_target_files.py (since LMP).
686 assert os.path.exists(path) and os.path.exists(mappath)
687
688 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
689 # it to clobbered_blocks so that it will be written to the target
690 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
691 clobbered_blocks = "0"
692
Tao Baoe709b092018-02-07 12:40:00 -0800693 image = sparse_img.SparseImage(path, mappath, clobbered_blocks,
694 allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -0800695
696 # block.map may contain less blocks, because mke2fs may skip allocating blocks
697 # if they contain all zeros. We can't reconstruct such a file from its block
698 # list. Tag such entries accordingly. (Bug: 65213616)
699 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800700 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700701 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800702 continue
703
Tom Cherryd14b8952018-08-09 14:26:00 -0700704 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
705 # filename listed in system.map may contain an additional leading slash
706 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
707 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700708 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
709
Tom Cherryd14b8952018-08-09 14:26:00 -0700710 # Special handling another case, where files not under /system
711 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700712 if which == 'system' and not arcname.startswith('SYSTEM'):
713 arcname = 'ROOT/' + arcname
714
715 assert arcname in input_zip.namelist(), \
716 "Failed to find the ZIP entry for {}".format(entry)
717
Tao Baoc765cca2018-01-31 17:32:40 -0800718 info = input_zip.getinfo(arcname)
719 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800720
721 # If a RangeSet has been tagged as using shared blocks while loading the
722 # image, its block list must be already incomplete due to that reason. Don't
723 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
724 if ranges.extra.get('uses_shared_blocks'):
725 continue
726
Tao Baoc765cca2018-01-31 17:32:40 -0800727 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
728 ranges.extra['incomplete'] = True
729
730 return image
731
732
Doug Zongkereef39442009-04-02 12:14:19 -0700733def GetKeyPasswords(keylist):
734 """Given a list of keys, prompt the user to enter passwords for
735 those which require them. Return a {key: password} dict. password
736 will be None if the key has no password."""
737
Doug Zongker8ce7c252009-05-22 13:34:54 -0700738 no_passwords = []
739 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700740 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700741 devnull = open("/dev/null", "w+b")
742 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800743 # We don't need a password for things that aren't really keys.
744 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700745 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700746 continue
747
T.R. Fullhart37e10522013-03-18 10:31:26 -0700748 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700749 "-inform", "DER", "-nocrypt"],
750 stdin=devnull.fileno(),
751 stdout=devnull.fileno(),
752 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700753 p.communicate()
754 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700755 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700756 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700757 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700758 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
759 "-inform", "DER", "-passin", "pass:"],
760 stdin=devnull.fileno(),
761 stdout=devnull.fileno(),
762 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700763 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700764 if p.returncode == 0:
765 # Encrypted key with empty string as password.
766 key_passwords[k] = ''
767 elif stderr.startswith('Error decrypting key'):
768 # Definitely encrypted key.
769 # It would have said "Error reading key" if it didn't parse correctly.
770 need_passwords.append(k)
771 else:
772 # Potentially, a type of key that openssl doesn't understand.
773 # We'll let the routines in signapk.jar handle it.
774 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700775 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700776
T.R. Fullhart37e10522013-03-18 10:31:26 -0700777 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800778 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700779 return key_passwords
780
781
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800782def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700783 """Gets the minSdkVersion declared in the APK.
784
785 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
786 This can be both a decimal number (API Level) or a codename.
787
788 Args:
789 apk_name: The APK filename.
790
791 Returns:
792 The parsed SDK version string.
793
794 Raises:
795 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800796 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700797 proc = Run(
798 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
799 stderr=subprocess.PIPE)
800 stdoutdata, stderrdata = proc.communicate()
801 if proc.returncode != 0:
802 raise ExternalError(
803 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
804 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800805
Tao Baof47bf0f2018-03-21 23:28:51 -0700806 for line in stdoutdata.split("\n"):
807 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800808 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
809 if m:
810 return m.group(1)
811 raise ExternalError("No minSdkVersion returned by aapt")
812
813
814def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700815 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800816
Tao Baof47bf0f2018-03-21 23:28:51 -0700817 If minSdkVersion is set to a codename, it is translated to a number using the
818 provided map.
819
820 Args:
821 apk_name: The APK filename.
822
823 Returns:
824 The parsed SDK version number.
825
826 Raises:
827 ExternalError: On failing to get the min SDK version number.
828 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800829 version = GetMinSdkVersion(apk_name)
830 try:
831 return int(version)
832 except ValueError:
833 # Not a decimal number. Codename?
834 if version in codename_to_api_level_map:
835 return codename_to_api_level_map[version]
836 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700837 raise ExternalError(
838 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
839 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800840
841
842def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800843 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700844 """Sign the input_name zip/jar/apk, producing output_name. Use the
845 given key and password (the latter may be None if the key does not
846 have a password.
847
Doug Zongker951495f2009-08-14 12:44:19 -0700848 If whole_file is true, use the "-w" option to SignApk to embed a
849 signature that covers the whole file in the archive comment of the
850 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800851
852 min_api_level is the API Level (int) of the oldest platform this file may end
853 up on. If not specified for an APK, the API Level is obtained by interpreting
854 the minSdkVersion attribute of the APK's AndroidManifest.xml.
855
856 codename_to_api_level_map is needed to translate the codename which may be
857 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700858 """
Tao Bao76def242017-11-21 09:25:31 -0800859 if codename_to_api_level_map is None:
860 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700861
Alex Klyubin9667b182015-12-10 13:38:50 -0800862 java_library_path = os.path.join(
863 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
864
Tao Baoe95540e2016-11-08 12:08:53 -0800865 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
866 ["-Djava.library.path=" + java_library_path,
867 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
868 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700869 if whole_file:
870 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800871
872 min_sdk_version = min_api_level
873 if min_sdk_version is None:
874 if not whole_file:
875 min_sdk_version = GetMinSdkVersionInt(
876 input_name, codename_to_api_level_map)
877 if min_sdk_version is not None:
878 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
879
T.R. Fullhart37e10522013-03-18 10:31:26 -0700880 cmd.extend([key + OPTIONS.public_key_suffix,
881 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800882 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700883
Tao Bao80921982018-03-21 21:02:19 -0700884 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
885 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700886 if password is not None:
887 password += "\n"
Tao Bao80921982018-03-21 21:02:19 -0700888 stdoutdata, _ = p.communicate(password)
Doug Zongkereef39442009-04-02 12:14:19 -0700889 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700890 raise ExternalError(
891 "Failed to run signapk.jar: return code {}:\n{}".format(
892 p.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700893
Doug Zongkereef39442009-04-02 12:14:19 -0700894
Doug Zongker37974732010-09-16 17:44:38 -0700895def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800896 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700897
Tao Bao9dd909e2017-11-14 11:27:32 -0800898 For non-AVB images, raise exception if the data is too big. Print a warning
899 if the data is nearing the maximum size.
900
901 For AVB images, the actual image size should be identical to the limit.
902
903 Args:
904 data: A string that contains all the data for the partition.
905 target: The partition name. The ".img" suffix is optional.
906 info_dict: The dict to be looked up for relevant info.
907 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700908 if target.endswith(".img"):
909 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700910 mount_point = "/" + target
911
Ying Wangf8824af2014-06-03 14:07:27 -0700912 fs_type = None
913 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700914 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700915 if mount_point == "/userdata":
916 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700917 p = info_dict["fstab"][mount_point]
918 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800919 device = p.device
920 if "/" in device:
921 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -0800922 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -0700923 if not fs_type or not limit:
924 return
Doug Zongkereef39442009-04-02 12:14:19 -0700925
Andrew Boie0f9aec82012-02-14 09:32:52 -0800926 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800927 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
928 # path.
929 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
930 if size != limit:
931 raise ExternalError(
932 "Mismatching image size for %s: expected %d actual %d" % (
933 target, limit, size))
934 else:
935 pct = float(size) * 100.0 / limit
936 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
937 if pct >= 99.0:
938 raise ExternalError(msg)
939 elif pct >= 95.0:
940 print("\n WARNING: %s\n" % (msg,))
941 elif OPTIONS.verbose:
942 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700943
944
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800945def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800946 """Parses the APK certs info from a given target-files zip.
947
948 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
949 tuple with the following elements: (1) a dictionary that maps packages to
950 certs (based on the "certificate" and "private_key" attributes in the file;
951 (2) a string representing the extension of compressed APKs in the target files
952 (e.g ".gz", ".bro").
953
954 Args:
955 tf_zip: The input target_files ZipFile (already open).
956
957 Returns:
958 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
959 the extension string of compressed APKs (e.g. ".gz"), or None if there's
960 no compressed APKs.
961 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800962 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100963 compressed_extension = None
964
Tao Bao0f990332017-09-08 19:02:54 -0700965 # META/apkcerts.txt contains the info for _all_ the packages known at build
966 # time. Filter out the ones that are not installed.
967 installed_files = set()
968 for name in tf_zip.namelist():
969 basename = os.path.basename(name)
970 if basename:
971 installed_files.add(basename)
972
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800973 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
974 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700975 if not line:
976 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800977 m = re.match(
978 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
979 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
980 line)
981 if not m:
982 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100983
Tao Bao818ddf52018-01-05 11:17:34 -0800984 matches = m.groupdict()
985 cert = matches["CERT"]
986 privkey = matches["PRIVKEY"]
987 name = matches["NAME"]
988 this_compressed_extension = matches["COMPRESSED"]
989
990 public_key_suffix_len = len(OPTIONS.public_key_suffix)
991 private_key_suffix_len = len(OPTIONS.private_key_suffix)
992 if cert in SPECIAL_CERT_STRINGS and not privkey:
993 certmap[name] = cert
994 elif (cert.endswith(OPTIONS.public_key_suffix) and
995 privkey.endswith(OPTIONS.private_key_suffix) and
996 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
997 certmap[name] = cert[:-public_key_suffix_len]
998 else:
999 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1000
1001 if not this_compressed_extension:
1002 continue
1003
1004 # Only count the installed files.
1005 filename = name + '.' + this_compressed_extension
1006 if filename not in installed_files:
1007 continue
1008
1009 # Make sure that all the values in the compression map have the same
1010 # extension. We don't support multiple compression methods in the same
1011 # system image.
1012 if compressed_extension:
1013 if this_compressed_extension != compressed_extension:
1014 raise ValueError(
1015 "Multiple compressed extensions: {} vs {}".format(
1016 compressed_extension, this_compressed_extension))
1017 else:
1018 compressed_extension = this_compressed_extension
1019
1020 return (certmap,
1021 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001022
1023
Doug Zongkereef39442009-04-02 12:14:19 -07001024COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001025Global options
1026
1027 -p (--path) <dir>
1028 Prepend <dir>/bin to the list of places to search for binaries run by this
1029 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001030
Doug Zongker05d3dea2009-06-22 11:32:31 -07001031 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001032 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001033
Tao Bao30df8b42018-04-23 15:32:53 -07001034 -x (--extra) <key=value>
1035 Add a key/value pair to the 'extras' dict, which device-specific extension
1036 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001037
Doug Zongkereef39442009-04-02 12:14:19 -07001038 -v (--verbose)
1039 Show command lines being executed.
1040
1041 -h (--help)
1042 Display this usage message and exit.
1043"""
1044
1045def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001046 print(docstring.rstrip("\n"))
1047 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001048
1049
1050def ParseOptions(argv,
1051 docstring,
1052 extra_opts="", extra_long_opts=(),
1053 extra_option_handler=None):
1054 """Parse the options in argv and return any arguments that aren't
1055 flags. docstring is the calling module's docstring, to be displayed
1056 for errors and -h. extra_opts and extra_long_opts are for flags
1057 defined by the caller, which are processed by passing them to
1058 extra_option_handler."""
1059
1060 try:
1061 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001062 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001063 ["help", "verbose", "path=", "signapk_path=",
1064 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001065 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001066 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1067 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001068 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001069 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001070 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001071 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001072 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001073 sys.exit(2)
1074
Doug Zongkereef39442009-04-02 12:14:19 -07001075 for o, a in opts:
1076 if o in ("-h", "--help"):
1077 Usage(docstring)
1078 sys.exit()
1079 elif o in ("-v", "--verbose"):
1080 OPTIONS.verbose = True
1081 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001082 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001083 elif o in ("--signapk_path",):
1084 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001085 elif o in ("--signapk_shared_library_path",):
1086 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001087 elif o in ("--extra_signapk_args",):
1088 OPTIONS.extra_signapk_args = shlex.split(a)
1089 elif o in ("--java_path",):
1090 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001091 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001092 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001093 elif o in ("--public_key_suffix",):
1094 OPTIONS.public_key_suffix = a
1095 elif o in ("--private_key_suffix",):
1096 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001097 elif o in ("--boot_signer_path",):
1098 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001099 elif o in ("--boot_signer_args",):
1100 OPTIONS.boot_signer_args = shlex.split(a)
1101 elif o in ("--verity_signer_path",):
1102 OPTIONS.verity_signer_path = a
1103 elif o in ("--verity_signer_args",):
1104 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001105 elif o in ("-s", "--device_specific"):
1106 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001107 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001108 key, value = a.split("=", 1)
1109 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001110 else:
1111 if extra_option_handler is None or not extra_option_handler(o, a):
1112 assert False, "unknown option \"%s\"" % (o,)
1113
Doug Zongker85448772014-09-09 14:59:20 -07001114 if OPTIONS.search_path:
1115 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1116 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001117
1118 return args
1119
1120
Tao Bao4c851b12016-09-19 13:54:38 -07001121def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001122 """Make a temp file and add it to the list of things to be deleted
1123 when Cleanup() is called. Return the filename."""
1124 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1125 os.close(fd)
1126 OPTIONS.tempfiles.append(fn)
1127 return fn
1128
1129
Tao Bao1c830bf2017-12-25 10:43:47 -08001130def MakeTempDir(prefix='tmp', suffix=''):
1131 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1132
1133 Returns:
1134 The absolute pathname of the new directory.
1135 """
1136 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1137 OPTIONS.tempfiles.append(dir_name)
1138 return dir_name
1139
1140
Doug Zongkereef39442009-04-02 12:14:19 -07001141def Cleanup():
1142 for i in OPTIONS.tempfiles:
1143 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001144 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001145 else:
1146 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001147 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001148
1149
1150class PasswordManager(object):
1151 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001152 self.editor = os.getenv("EDITOR")
1153 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001154
1155 def GetPasswords(self, items):
1156 """Get passwords corresponding to each string in 'items',
1157 returning a dict. (The dict may have keys in addition to the
1158 values in 'items'.)
1159
1160 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1161 user edit that file to add more needed passwords. If no editor is
1162 available, or $ANDROID_PW_FILE isn't define, prompts the user
1163 interactively in the ordinary way.
1164 """
1165
1166 current = self.ReadFile()
1167
1168 first = True
1169 while True:
1170 missing = []
1171 for i in items:
1172 if i not in current or not current[i]:
1173 missing.append(i)
1174 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001175 if not missing:
1176 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001177
1178 for i in missing:
1179 current[i] = ""
1180
1181 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001182 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001183 answer = raw_input("try to edit again? [y]> ").strip()
1184 if answer and answer[0] not in 'yY':
1185 raise RuntimeError("key passwords unavailable")
1186 first = False
1187
1188 current = self.UpdateAndReadFile(current)
1189
Dan Albert8b72aef2015-03-23 19:13:21 -07001190 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001191 """Prompt the user to enter a value (password) for each key in
1192 'current' whose value is fales. Returns a new dict with all the
1193 values.
1194 """
1195 result = {}
1196 for k, v in sorted(current.iteritems()):
1197 if v:
1198 result[k] = v
1199 else:
1200 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001201 result[k] = getpass.getpass(
1202 "Enter password for %s key> " % k).strip()
1203 if result[k]:
1204 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001205 return result
1206
1207 def UpdateAndReadFile(self, current):
1208 if not self.editor or not self.pwfile:
1209 return self.PromptResult(current)
1210
1211 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001212 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001213 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1214 f.write("# (Additional spaces are harmless.)\n\n")
1215
1216 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001217 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1218 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001219 f.write("[[[ %s ]]] %s\n" % (v, k))
1220 if not v and first_line is None:
1221 # position cursor on first line with no password.
1222 first_line = i + 4
1223 f.close()
1224
1225 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1226 _, _ = p.communicate()
1227
1228 return self.ReadFile()
1229
1230 def ReadFile(self):
1231 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001232 if self.pwfile is None:
1233 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001234 try:
1235 f = open(self.pwfile, "r")
1236 for line in f:
1237 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001238 if not line or line[0] == '#':
1239 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001240 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1241 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001242 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001243 else:
1244 result[m.group(2)] = m.group(1)
1245 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001246 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001247 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001248 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001249 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001250
1251
Dan Albert8e0178d2015-01-27 15:53:15 -08001252def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1253 compress_type=None):
1254 import datetime
1255
1256 # http://b/18015246
1257 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1258 # for files larger than 2GiB. We can work around this by adjusting their
1259 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1260 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1261 # it isn't clear to me exactly what circumstances cause this).
1262 # `zipfile.write()` must be used directly to work around this.
1263 #
1264 # This mess can be avoided if we port to python3.
1265 saved_zip64_limit = zipfile.ZIP64_LIMIT
1266 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1267
1268 if compress_type is None:
1269 compress_type = zip_file.compression
1270 if arcname is None:
1271 arcname = filename
1272
1273 saved_stat = os.stat(filename)
1274
1275 try:
1276 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1277 # file to be zipped and reset it when we're done.
1278 os.chmod(filename, perms)
1279
1280 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001281 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1282 # intentional. zip stores datetimes in local time without a time zone
1283 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1284 # in the zip archive.
1285 local_epoch = datetime.datetime.fromtimestamp(0)
1286 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001287 os.utime(filename, (timestamp, timestamp))
1288
1289 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1290 finally:
1291 os.chmod(filename, saved_stat.st_mode)
1292 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1293 zipfile.ZIP64_LIMIT = saved_zip64_limit
1294
1295
Tao Bao58c1b962015-05-20 09:32:18 -07001296def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001297 compress_type=None):
1298 """Wrap zipfile.writestr() function to work around the zip64 limit.
1299
1300 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1301 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1302 when calling crc32(bytes).
1303
1304 But it still works fine to write a shorter string into a large zip file.
1305 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1306 when we know the string won't be too long.
1307 """
1308
1309 saved_zip64_limit = zipfile.ZIP64_LIMIT
1310 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1311
1312 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1313 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001314 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001315 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001316 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001317 else:
Tao Baof3282b42015-04-01 11:21:55 -07001318 zinfo = zinfo_or_arcname
1319
1320 # If compress_type is given, it overrides the value in zinfo.
1321 if compress_type is not None:
1322 zinfo.compress_type = compress_type
1323
Tao Bao58c1b962015-05-20 09:32:18 -07001324 # If perms is given, it has a priority.
1325 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001326 # If perms doesn't set the file type, mark it as a regular file.
1327 if perms & 0o770000 == 0:
1328 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001329 zinfo.external_attr = perms << 16
1330
Tao Baof3282b42015-04-01 11:21:55 -07001331 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001332 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1333
Dan Albert8b72aef2015-03-23 19:13:21 -07001334 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001335 zipfile.ZIP64_LIMIT = saved_zip64_limit
1336
1337
Tao Bao89d7ab22017-12-14 17:05:33 -08001338def ZipDelete(zip_filename, entries):
1339 """Deletes entries from a ZIP file.
1340
1341 Since deleting entries from a ZIP file is not supported, it shells out to
1342 'zip -d'.
1343
1344 Args:
1345 zip_filename: The name of the ZIP file.
1346 entries: The name of the entry, or the list of names to be deleted.
1347
1348 Raises:
1349 AssertionError: In case of non-zero return from 'zip'.
1350 """
1351 if isinstance(entries, basestring):
1352 entries = [entries]
1353 cmd = ["zip", "-d", zip_filename] + entries
1354 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1355 stdoutdata, _ = proc.communicate()
1356 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1357 stdoutdata)
1358
1359
Tao Baof3282b42015-04-01 11:21:55 -07001360def ZipClose(zip_file):
1361 # http://b/18015246
1362 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1363 # central directory.
1364 saved_zip64_limit = zipfile.ZIP64_LIMIT
1365 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1366
1367 zip_file.close()
1368
1369 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001370
1371
1372class DeviceSpecificParams(object):
1373 module = None
1374 def __init__(self, **kwargs):
1375 """Keyword arguments to the constructor become attributes of this
1376 object, which is passed to all functions in the device-specific
1377 module."""
1378 for k, v in kwargs.iteritems():
1379 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001380 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001381
1382 if self.module is None:
1383 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001384 if not path:
1385 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001386 try:
1387 if os.path.isdir(path):
1388 info = imp.find_module("releasetools", [path])
1389 else:
1390 d, f = os.path.split(path)
1391 b, x = os.path.splitext(f)
1392 if x == ".py":
1393 f = b
1394 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001395 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001396 self.module = imp.load_module("device_specific", *info)
1397 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001398 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001399
1400 def _DoCall(self, function_name, *args, **kwargs):
1401 """Call the named function in the device-specific module, passing
1402 the given args and kwargs. The first argument to the call will be
1403 the DeviceSpecific object itself. If there is no module, or the
1404 module does not define the function, return the value of the
1405 'default' kwarg (which itself defaults to None)."""
1406 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001407 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001408 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1409
1410 def FullOTA_Assertions(self):
1411 """Called after emitting the block of assertions at the top of a
1412 full OTA package. Implementations can add whatever additional
1413 assertions they like."""
1414 return self._DoCall("FullOTA_Assertions")
1415
Doug Zongkere5ff5902012-01-17 10:55:37 -08001416 def FullOTA_InstallBegin(self):
1417 """Called at the start of full OTA installation."""
1418 return self._DoCall("FullOTA_InstallBegin")
1419
Doug Zongker05d3dea2009-06-22 11:32:31 -07001420 def FullOTA_InstallEnd(self):
1421 """Called at the end of full OTA installation; typically this is
1422 used to install the image for the device's baseband processor."""
1423 return self._DoCall("FullOTA_InstallEnd")
1424
1425 def IncrementalOTA_Assertions(self):
1426 """Called after emitting the block of assertions at the top of an
1427 incremental OTA package. Implementations can add whatever
1428 additional assertions they like."""
1429 return self._DoCall("IncrementalOTA_Assertions")
1430
Doug Zongkere5ff5902012-01-17 10:55:37 -08001431 def IncrementalOTA_VerifyBegin(self):
1432 """Called at the start of the verification phase of incremental
1433 OTA installation; additional checks can be placed here to abort
1434 the script before any changes are made."""
1435 return self._DoCall("IncrementalOTA_VerifyBegin")
1436
Doug Zongker05d3dea2009-06-22 11:32:31 -07001437 def IncrementalOTA_VerifyEnd(self):
1438 """Called at the end of the verification phase of incremental OTA
1439 installation; additional checks can be placed here to abort the
1440 script before any changes are made."""
1441 return self._DoCall("IncrementalOTA_VerifyEnd")
1442
Doug Zongkere5ff5902012-01-17 10:55:37 -08001443 def IncrementalOTA_InstallBegin(self):
1444 """Called at the start of incremental OTA installation (after
1445 verification is complete)."""
1446 return self._DoCall("IncrementalOTA_InstallBegin")
1447
Doug Zongker05d3dea2009-06-22 11:32:31 -07001448 def IncrementalOTA_InstallEnd(self):
1449 """Called at the end of incremental OTA installation; typically
1450 this is used to install the image for the device's baseband
1451 processor."""
1452 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001453
Tao Bao9bc6bb22015-11-09 16:58:28 -08001454 def VerifyOTA_Assertions(self):
1455 return self._DoCall("VerifyOTA_Assertions")
1456
Tao Bao76def242017-11-21 09:25:31 -08001457
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001458class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001459 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001460 self.name = name
1461 self.data = data
1462 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001463 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001464 self.sha1 = sha1(data).hexdigest()
1465
1466 @classmethod
1467 def FromLocalFile(cls, name, diskname):
1468 f = open(diskname, "rb")
1469 data = f.read()
1470 f.close()
1471 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001472
1473 def WriteToTemp(self):
1474 t = tempfile.NamedTemporaryFile()
1475 t.write(self.data)
1476 t.flush()
1477 return t
1478
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001479 def WriteToDir(self, d):
1480 with open(os.path.join(d, self.name), "wb") as fp:
1481 fp.write(self.data)
1482
Geremy Condra36bd3652014-02-06 19:45:10 -08001483 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001484 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001485
Tao Bao76def242017-11-21 09:25:31 -08001486
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001487DIFF_PROGRAM_BY_EXT = {
1488 ".gz" : "imgdiff",
1489 ".zip" : ["imgdiff", "-z"],
1490 ".jar" : ["imgdiff", "-z"],
1491 ".apk" : ["imgdiff", "-z"],
1492 ".img" : "imgdiff",
1493 }
1494
Tao Bao76def242017-11-21 09:25:31 -08001495
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001496class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001497 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001498 self.tf = tf
1499 self.sf = sf
1500 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001501 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001502
1503 def ComputePatch(self):
1504 """Compute the patch (as a string of data) needed to turn sf into
1505 tf. Returns the same tuple as GetPatch()."""
1506
1507 tf = self.tf
1508 sf = self.sf
1509
Doug Zongker24cd2802012-08-14 16:36:15 -07001510 if self.diff_program:
1511 diff_program = self.diff_program
1512 else:
1513 ext = os.path.splitext(tf.name)[1]
1514 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001515
1516 ttemp = tf.WriteToTemp()
1517 stemp = sf.WriteToTemp()
1518
1519 ext = os.path.splitext(tf.name)[1]
1520
1521 try:
1522 ptemp = tempfile.NamedTemporaryFile()
1523 if isinstance(diff_program, list):
1524 cmd = copy.copy(diff_program)
1525 else:
1526 cmd = [diff_program]
1527 cmd.append(stemp.name)
1528 cmd.append(ttemp.name)
1529 cmd.append(ptemp.name)
1530 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001531 err = []
1532 def run():
1533 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001534 if e:
1535 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001536 th = threading.Thread(target=run)
1537 th.start()
1538 th.join(timeout=300) # 5 mins
1539 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001540 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001541 p.terminate()
1542 th.join(5)
1543 if th.is_alive():
1544 p.kill()
1545 th.join()
1546
Tianjie Xua2a9f992018-01-05 15:15:54 -08001547 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001548 print("WARNING: failure running %s:\n%s\n" % (
1549 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001550 self.patch = None
1551 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001552 diff = ptemp.read()
1553 finally:
1554 ptemp.close()
1555 stemp.close()
1556 ttemp.close()
1557
1558 self.patch = diff
1559 return self.tf, self.sf, self.patch
1560
1561
1562 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001563 """Returns a tuple of (target_file, source_file, patch_data).
1564
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001565 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001566 computing the patch failed.
1567 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001568 return self.tf, self.sf, self.patch
1569
1570
1571def ComputeDifferences(diffs):
1572 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001573 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001574
1575 # Do the largest files first, to try and reduce the long-pole effect.
1576 by_size = [(i.tf.size, i) for i in diffs]
1577 by_size.sort(reverse=True)
1578 by_size = [i[1] for i in by_size]
1579
1580 lock = threading.Lock()
1581 diff_iter = iter(by_size) # accessed under lock
1582
1583 def worker():
1584 try:
1585 lock.acquire()
1586 for d in diff_iter:
1587 lock.release()
1588 start = time.time()
1589 d.ComputePatch()
1590 dur = time.time() - start
1591 lock.acquire()
1592
1593 tf, sf, patch = d.GetPatch()
1594 if sf.name == tf.name:
1595 name = tf.name
1596 else:
1597 name = "%s (%s)" % (tf.name, sf.name)
1598 if patch is None:
Tao Bao76def242017-11-21 09:25:31 -08001599 print(
1600 "patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001601 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001602 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1603 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001604 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001605 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001606 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001607 raise
1608
1609 # start worker threads; wait for them all to finish.
1610 threads = [threading.Thread(target=worker)
1611 for i in range(OPTIONS.worker_threads)]
1612 for th in threads:
1613 th.start()
1614 while threads:
1615 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001616
1617
Dan Albert8b72aef2015-03-23 19:13:21 -07001618class BlockDifference(object):
1619 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001620 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001621 self.tgt = tgt
1622 self.src = src
1623 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001624 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001625 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001626
Tao Baodd2a5892015-03-12 12:32:37 -07001627 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001628 version = max(
1629 int(i) for i in
1630 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001631 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001632 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001633
1634 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001635 version=self.version,
1636 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001637 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001638 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001639 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001640 self.touched_src_ranges = b.touched_src_ranges
1641 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001642
Tao Baoaac4ad52015-10-16 15:26:34 -07001643 if src is None:
1644 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1645 else:
1646 _, self.device = GetTypeAndDevice("/" + partition,
1647 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001648
Tao Baod8d14be2016-02-04 14:26:02 -08001649 @property
1650 def required_cache(self):
1651 return self._required_cache
1652
Tao Bao76def242017-11-21 09:25:31 -08001653 def WriteScript(self, script, output_zip, progress=None,
1654 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001655 if not self.src:
1656 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001657 script.Print("Patching %s image unconditionally..." % (self.partition,))
1658 else:
1659 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001660
Dan Albert8b72aef2015-03-23 19:13:21 -07001661 if progress:
1662 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001663 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001664
1665 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001666 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001667
Tao Bao9bc6bb22015-11-09 16:58:28 -08001668 def WriteStrictVerifyScript(self, script):
1669 """Verify all the blocks in the care_map, including clobbered blocks.
1670
1671 This differs from the WriteVerifyScript() function: a) it prints different
1672 error messages; b) it doesn't allow half-way updated images to pass the
1673 verification."""
1674
1675 partition = self.partition
1676 script.Print("Verifying %s..." % (partition,))
1677 ranges = self.tgt.care_map
1678 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001679 script.AppendExtra(
1680 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1681 'ui_print("\\"%s\\" has unexpected contents.");' % (
1682 self.device, ranges_str,
1683 self.tgt.TotalSha1(include_clobbered_blocks=True),
1684 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001685 script.AppendExtra("")
1686
Tao Baod522bdc2016-04-12 15:53:16 -07001687 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001688 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001689
1690 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001691 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001692 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001693
1694 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001695 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001696 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001697 ranges = self.touched_src_ranges
1698 expected_sha1 = self.touched_src_sha1
1699 else:
1700 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1701 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001702
1703 # No blocks to be checked, skipping.
1704 if not ranges:
1705 return
1706
Tao Bao5ece99d2015-05-12 11:42:31 -07001707 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001708 script.AppendExtra(
1709 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1710 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1711 '"%s.patch.dat")) then' % (
1712 self.device, ranges_str, expected_sha1,
1713 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001714 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001715 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001716
Tianjie Xufc3422a2015-12-15 11:53:59 -08001717 if self.version >= 4:
1718
1719 # Bug: 21124327
1720 # When generating incrementals for the system and vendor partitions in
1721 # version 4 or newer, explicitly check the first block (which contains
1722 # the superblock) of the partition to see if it's what we expect. If
1723 # this check fails, give an explicit log message about the partition
1724 # having been remounted R/W (the most likely explanation).
1725 if self.check_first_block:
1726 script.AppendExtra('check_first_block("%s");' % (self.device,))
1727
1728 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001729 if partition == "system":
1730 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1731 else:
1732 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001733 script.AppendExtra((
1734 'ifelse (block_image_recover("{device}", "{ranges}") && '
1735 'block_image_verify("{device}", '
1736 'package_extract_file("{partition}.transfer.list"), '
1737 '"{partition}.new.dat", "{partition}.patch.dat"), '
1738 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001739 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001740 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001741 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001742
Tao Baodd2a5892015-03-12 12:32:37 -07001743 # Abort the OTA update. Note that the incremental OTA cannot be applied
1744 # even if it may match the checksum of the target partition.
1745 # a) If version < 3, operations like move and erase will make changes
1746 # unconditionally and damage the partition.
1747 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001748 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001749 if partition == "system":
1750 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1751 else:
1752 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1753 script.AppendExtra((
1754 'abort("E%d: %s partition has unexpected contents");\n'
1755 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001756
Tao Bao5fcaaef2015-06-01 13:40:49 -07001757 def _WritePostInstallVerifyScript(self, script):
1758 partition = self.partition
1759 script.Print('Verifying the updated %s image...' % (partition,))
1760 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1761 ranges = self.tgt.care_map
1762 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001763 script.AppendExtra(
1764 'if range_sha1("%s", "%s") == "%s" then' % (
1765 self.device, ranges_str,
1766 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001767
1768 # Bug: 20881595
1769 # Verify that extended blocks are really zeroed out.
1770 if self.tgt.extended:
1771 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001772 script.AppendExtra(
1773 'if range_sha1("%s", "%s") == "%s" then' % (
1774 self.device, ranges_str,
1775 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001776 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001777 if partition == "system":
1778 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1779 else:
1780 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001781 script.AppendExtra(
1782 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001783 ' abort("E%d: %s partition has unexpected non-zero contents after '
1784 'OTA update");\n'
1785 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001786 else:
1787 script.Print('Verified the updated %s image.' % (partition,))
1788
Tianjie Xu209db462016-05-24 17:34:52 -07001789 if partition == "system":
1790 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1791 else:
1792 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1793
Tao Bao5fcaaef2015-06-01 13:40:49 -07001794 script.AppendExtra(
1795 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001796 ' abort("E%d: %s partition has unexpected contents after OTA '
1797 'update");\n'
1798 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001799
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001800 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001801 ZipWrite(output_zip,
1802 '{}.transfer.list'.format(self.path),
1803 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001804
Tao Bao76def242017-11-21 09:25:31 -08001805 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1806 # its size. Quailty 9 almost triples the compression time but doesn't
1807 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001808 # zip | brotli(quality 6) | brotli(quality 9)
1809 # compressed_size: 942M | 869M (~8% reduced) | 854M
1810 # compression_time: 75s | 265s | 719s
1811 # decompression_time: 15s | 25s | 25s
1812
1813 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001814 brotli_cmd = ['brotli', '--quality=6',
1815 '--output={}.new.dat.br'.format(self.path),
1816 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001817 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao80921982018-03-21 21:02:19 -07001818 p = Run(brotli_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1819 stdoutdata, _ = p.communicate()
1820 assert p.returncode == 0, \
1821 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1822 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001823
1824 new_data_name = '{}.new.dat.br'.format(self.partition)
1825 ZipWrite(output_zip,
1826 '{}.new.dat.br'.format(self.path),
1827 new_data_name,
1828 compress_type=zipfile.ZIP_STORED)
1829 else:
1830 new_data_name = '{}.new.dat'.format(self.partition)
1831 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1832
Dan Albert8e0178d2015-01-27 15:53:15 -08001833 ZipWrite(output_zip,
1834 '{}.patch.dat'.format(self.path),
1835 '{}.patch.dat'.format(self.partition),
1836 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001837
Tianjie Xu209db462016-05-24 17:34:52 -07001838 if self.partition == "system":
1839 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1840 else:
1841 code = ErrorCode.VENDOR_UPDATE_FAILURE
1842
Dan Albert8e0178d2015-01-27 15:53:15 -08001843 call = ('block_image_update("{device}", '
1844 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001845 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001846 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001847 device=self.device, partition=self.partition,
1848 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001849 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001850
Dan Albert8b72aef2015-03-23 19:13:21 -07001851 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001852 data = source.ReadRangeSet(ranges)
1853 ctx = sha1()
1854
1855 for p in data:
1856 ctx.update(p)
1857
1858 return ctx.hexdigest()
1859
Tao Baoe9b61912015-07-09 17:37:49 -07001860 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1861 """Return the hash value for all zero blocks."""
1862 zero_block = '\x00' * 4096
1863 ctx = sha1()
1864 for _ in range(num_blocks):
1865 ctx.update(zero_block)
1866
1867 return ctx.hexdigest()
1868
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001869
1870DataImage = blockimgdiff.DataImage
1871
Tao Bao76def242017-11-21 09:25:31 -08001872
Doug Zongker96a57e72010-09-26 14:57:41 -07001873# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001874PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001875 "ext4": "EMMC",
1876 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001877 "f2fs": "EMMC",
1878 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001879}
Doug Zongker96a57e72010-09-26 14:57:41 -07001880
Tao Bao76def242017-11-21 09:25:31 -08001881
Doug Zongker96a57e72010-09-26 14:57:41 -07001882def GetTypeAndDevice(mount_point, info):
1883 fstab = info["fstab"]
1884 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001885 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1886 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001887 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001888 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001889
1890
1891def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001892 """Parses and converts a PEM-encoded certificate into DER-encoded.
1893
1894 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1895
1896 Returns:
1897 The decoded certificate string.
1898 """
1899 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001900 save = False
1901 for line in data.split("\n"):
1902 if "--END CERTIFICATE--" in line:
1903 break
1904 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001905 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001906 if "--BEGIN CERTIFICATE--" in line:
1907 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001908 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001909 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001910
Tao Bao04e1f012018-02-04 12:13:35 -08001911
1912def ExtractPublicKey(cert):
1913 """Extracts the public key (PEM-encoded) from the given certificate file.
1914
1915 Args:
1916 cert: The certificate filename.
1917
1918 Returns:
1919 The public key string.
1920
1921 Raises:
1922 AssertionError: On non-zero return from 'openssl'.
1923 """
1924 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1925 # While openssl 1.1 writes the key into the given filename followed by '-out',
1926 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1927 # stdout instead.
1928 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1929 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1930 pubkey, stderrdata = proc.communicate()
1931 assert proc.returncode == 0, \
1932 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1933 return pubkey
1934
1935
Doug Zongker412c02f2014-02-13 10:58:24 -08001936def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1937 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001938 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001939
Tao Bao6d5d6232018-03-09 17:04:42 -08001940 Most of the space in the boot and recovery images is just the kernel, which is
1941 identical for the two, so the resulting patch should be efficient. Add it to
1942 the output zip, along with a shell script that is run from init.rc on first
1943 boot to actually do the patching and install the new recovery image.
1944
1945 Args:
1946 input_dir: The top-level input directory of the target-files.zip.
1947 output_sink: The callback function that writes the result.
1948 recovery_img: File object for the recovery image.
1949 boot_img: File objects for the boot image.
1950 info_dict: A dict returned by common.LoadInfoDict() on the input
1951 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001952 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001953 if info_dict is None:
1954 info_dict = OPTIONS.info_dict
1955
Tao Bao6d5d6232018-03-09 17:04:42 -08001956 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001957
Tao Baof2cffbd2015-07-22 12:33:18 -07001958 if full_recovery_image:
1959 output_sink("etc/recovery.img", recovery_img.data)
1960
1961 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08001962 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07001963 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08001964 # With system-root-image, boot and recovery images will have mismatching
1965 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
1966 # to handle such a case.
1967 if system_root_image:
1968 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07001969 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08001970 assert not os.path.exists(path)
1971 else:
1972 diff_program = ["imgdiff"]
1973 if os.path.exists(path):
1974 diff_program.append("-b")
1975 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07001976 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08001977 else:
1978 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07001979
1980 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1981 _, _, patch = d.ComputePatch()
1982 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001983
Dan Albertebb19aa2015-03-27 19:11:53 -07001984 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001985 # The following GetTypeAndDevice()s need to use the path in the target
1986 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001987 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1988 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1989 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001990 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001991
Tao Baof2cffbd2015-07-22 12:33:18 -07001992 if full_recovery_image:
1993 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07001994if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
1995 applypatch \\
1996 --flash /system/etc/recovery.img \\
1997 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
1998 log -t recovery "Installing new recovery image: succeeded" || \\
1999 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002000else
2001 log -t recovery "Recovery image already installed"
2002fi
2003""" % {'type': recovery_type,
2004 'device': recovery_device,
2005 'sha1': recovery_img.sha1,
2006 'size': recovery_img.size}
2007 else:
2008 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002009if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2010 applypatch %(bonus_args)s \\
2011 --patch /system/recovery-from-boot.p \\
2012 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2013 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2014 log -t recovery "Installing new recovery image: succeeded" || \\
2015 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002016else
2017 log -t recovery "Recovery image already installed"
2018fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002019""" % {'boot_size': boot_img.size,
2020 'boot_sha1': boot_img.sha1,
2021 'recovery_size': recovery_img.size,
2022 'recovery_sha1': recovery_img.sha1,
2023 'boot_type': boot_type,
2024 'boot_device': boot_device,
2025 'recovery_type': recovery_type,
2026 'recovery_device': recovery_device,
2027 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002028
2029 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002030 # in the L release.
2031 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002032
Tao Bao89fbb0f2017-01-10 10:47:58 -08002033 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002034
2035 output_sink(sh_location, sh)