blob: 08f17916b557b98a63c371c7961d59a022d8ec8b [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Doug Zongkerea5d7a92010-09-12 15:26:16 -070017import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070018import errno
Doug Zongkereef39442009-04-02 12:14:19 -070019import getopt
20import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010021import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070022import imp
Doug Zongkereef39442009-04-02 12:14:19 -070023import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080024import platform
Doug Zongkereef39442009-04-02 12:14:19 -070025import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070026import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070027import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080028import string
Doug Zongkereef39442009-04-02 12:14:19 -070029import subprocess
30import sys
31import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070032import threading
33import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070034import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080035from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070036
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070037import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080038import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070039
Dan Albert8b72aef2015-03-23 19:13:21 -070040class Options(object):
41 def __init__(self):
42 platform_search_path = {
43 "linux2": "out/host/linux-x86",
44 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070045 }
Doug Zongker85448772014-09-09 14:59:20 -070046
Tao Bao76def242017-11-21 09:25:31 -080047 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080049 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070050 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080052 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070053 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070055 # use otatools built boot_signer by default
56 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070057 self.boot_signer_args = []
58 self.verity_signer_path = None
59 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.verbose = False
61 self.tempfiles = []
62 self.device_specific = None
63 self.extras = {}
64 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070065 self.source_info_dict = None
66 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070068 # Stash size cannot exceed cache_size * threshold.
69 self.cache_size = None
70 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070071
72
73OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070074
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080075
76# Values for "certificate" in apkcerts that mean special things.
77SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
78
Tao Bao9dd909e2017-11-14 11:27:32 -080079
80# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010081AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010082 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080083
84
Tianjie Xu209db462016-05-24 17:34:52 -070085class ErrorCode(object):
86 """Define error_codes for failures that happen during the actual
87 update package installation.
88
89 Error codes 0-999 are reserved for failures before the package
90 installation (i.e. low battery, package verification failure).
91 Detailed code in 'bootable/recovery/error_code.h' """
92
93 SYSTEM_VERIFICATION_FAILURE = 1000
94 SYSTEM_UPDATE_FAILURE = 1001
95 SYSTEM_UNEXPECTED_CONTENTS = 1002
96 SYSTEM_NONZERO_CONTENTS = 1003
97 SYSTEM_RECOVER_FAILURE = 1004
98 VENDOR_VERIFICATION_FAILURE = 2000
99 VENDOR_UPDATE_FAILURE = 2001
100 VENDOR_UNEXPECTED_CONTENTS = 2002
101 VENDOR_NONZERO_CONTENTS = 2003
102 VENDOR_RECOVER_FAILURE = 2004
103 OEM_PROP_MISMATCH = 3000
104 FINGERPRINT_MISMATCH = 3001
105 THUMBPRINT_MISMATCH = 3002
106 OLDER_BUILD = 3003
107 DEVICE_MISMATCH = 3004
108 BAD_PATCH_FILE = 3005
109 INSUFFICIENT_CACHE_SPACE = 3006
110 TUNE_PARTITION_FAILURE = 3007
111 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800112
Tao Bao80921982018-03-21 21:02:19 -0700113
Dan Albert8b72aef2015-03-23 19:13:21 -0700114class ExternalError(RuntimeError):
115 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700116
117
Tao Bao39451582017-05-04 11:10:47 -0700118def Run(args, verbose=None, **kwargs):
119 """Create and return a subprocess.Popen object.
120
121 Caller can specify if the command line should be printed. The global
122 OPTIONS.verbose will be used if not specified.
123 """
124 if verbose is None:
125 verbose = OPTIONS.verbose
126 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800127 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700128 return subprocess.Popen(args, **kwargs)
129
130
Tao Baoc765cca2018-01-31 17:32:40 -0800131def RoundUpTo4K(value):
132 rounded_up = value + 4095
133 return rounded_up - (rounded_up % 4096)
134
135
Ying Wang7e6d4e42010-12-13 16:25:36 -0800136def CloseInheritedPipes():
137 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
138 before doing other work."""
139 if platform.system() != "Darwin":
140 return
141 for d in range(3, 1025):
142 try:
143 stat = os.fstat(d)
144 if stat is not None:
145 pipebit = stat[0] & 0x1000
146 if pipebit != 0:
147 os.close(d)
148 except OSError:
149 pass
150
151
Tao Bao2c15d9e2015-07-09 11:51:16 -0700152def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700153 """Read and parse the META/misc_info.txt key/value pairs from the
154 input target files and return a dict."""
155
Doug Zongkerc9253822014-02-04 12:17:58 -0800156 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700157 if isinstance(input_file, zipfile.ZipFile):
158 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800159 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700160 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800161 try:
162 with open(path) as f:
163 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700164 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800165 if e.errno == errno.ENOENT:
166 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800167
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700168 try:
Michael Runge6e836112014-04-15 17:40:21 -0700169 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700170 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800171 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700172
Tao Bao6cd54732017-02-27 15:12:05 -0800173 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800174 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800175
Tao Bao84e75682015-07-19 02:38:53 -0700176 # A few properties are stored as links to the files in the out/ directory.
177 # It works fine with the build system. However, they are no longer available
178 # when (re)generating from target_files zip. If input_dir is not None, we
179 # are doing repacking. Redirect those properties to the actual files in the
180 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700181 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400182 # We carry a copy of file_contexts.bin under META/. If not available,
183 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700184 # to build images than the one running on device, such as when enabling
185 # system_root_image. In that case, we must have the one for image
186 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700187 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
188 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700189 if d.get("system_root_image") == "true":
190 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700191 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700192 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700193 if not os.path.exists(fc_config):
194 fc_config = None
195
196 if fc_config:
197 d["selinux_fc"] = fc_config
198
Tao Bao8bfd3c72018-07-20 15:20:28 -0700199 # Similarly we need to redirect "root_dir" and "root_fs_config".
Tao Bao84e75682015-07-19 02:38:53 -0700200 if d.get("system_root_image") == "true":
Tao Bao8bfd3c72018-07-20 15:20:28 -0700201 d["root_dir"] = os.path.join(input_dir, "ROOT")
202 d["root_fs_config"] = os.path.join(
Tao Bao84e75682015-07-19 02:38:53 -0700203 input_dir, "META", "root_filesystem_config.txt")
204
Tao Baof54216f2016-03-29 15:12:37 -0700205 # Redirect {system,vendor}_base_fs_file.
206 if "system_base_fs_file" in d:
207 basename = os.path.basename(d["system_base_fs_file"])
208 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700209 if os.path.exists(system_base_fs_file):
210 d["system_base_fs_file"] = system_base_fs_file
211 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800212 print("Warning: failed to find system base fs file: %s" % (
213 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700214 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700215
216 if "vendor_base_fs_file" in d:
217 basename = os.path.basename(d["vendor_base_fs_file"])
218 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700219 if os.path.exists(vendor_base_fs_file):
220 d["vendor_base_fs_file"] = vendor_base_fs_file
221 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800222 print("Warning: failed to find vendor base fs file: %s" % (
223 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700224 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700225
Doug Zongker37974732010-09-16 17:44:38 -0700226 def makeint(key):
227 if key in d:
228 d[key] = int(d[key], 0)
229
230 makeint("recovery_api_version")
231 makeint("blocksize")
232 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700233 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700234 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700235 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700236 makeint("recovery_size")
237 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800238 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700239
Tao Baoa57ab9f2018-08-24 12:08:38 -0700240 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
241 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
242 # cases, since it may load the info_dict from an old build (e.g. when
243 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800244 system_root_image = d.get("system_root_image") == "true"
245 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700246 recovery_fstab_path = "RECOVERY/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 = "RECOVERY/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 = "RECOVERY/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
Tao Bao76def242017-11-21 09:25:31 -0800257 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700258 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700259 if isinstance(input_file, zipfile.ZipFile):
260 if recovery_fstab_path not in input_file.namelist():
261 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
262 else:
263 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
264 if not os.path.exists(path):
265 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800266 d["fstab"] = LoadRecoveryFSTab(
267 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700268
Tianjie Xucfa86222016-03-07 16:31:19 -0800269 else:
270 d["fstab"] = None
271
Tao Baobcd1d162017-08-26 13:10:26 -0700272 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
273 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Tao Bao12d87fc2018-01-31 12:18:52 -0800274
275 # Set up the salt (based on fingerprint or thumbprint) that will be used when
276 # adding AVB footer.
277 if d.get("avb_enable") == "true":
278 fp = None
279 if "build.prop" in d:
280 build_prop = d["build.prop"]
281 if "ro.build.fingerprint" in build_prop:
282 fp = build_prop["ro.build.fingerprint"]
283 elif "ro.build.thumbprint" in build_prop:
284 fp = build_prop["ro.build.thumbprint"]
285 if fp:
286 d["avb_salt"] = sha256(fp).hexdigest()
287
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700288 return d
289
Tao Baod1de6f32017-03-01 16:38:48 -0800290
Tao Baobcd1d162017-08-26 13:10:26 -0700291def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700292 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700293 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700294 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700295 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700296 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700297 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700298
Tao Baod1de6f32017-03-01 16:38:48 -0800299
Michael Runge6e836112014-04-15 17:40:21 -0700300def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700301 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700302 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700303 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700304 if not line or line.startswith("#"):
305 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700306 if "=" in line:
307 name, value = line.split("=", 1)
308 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700309 return d
310
Tao Baod1de6f32017-03-01 16:38:48 -0800311
Tianjie Xucfa86222016-03-07 16:31:19 -0800312def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
313 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700314 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800315 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700316 self.mount_point = mount_point
317 self.fs_type = fs_type
318 self.device = device
319 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700320 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700321
322 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800323 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700324 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800325 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700326 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700327
Tao Baod1de6f32017-03-01 16:38:48 -0800328 assert fstab_version == 2
329
330 d = {}
331 for line in data.split("\n"):
332 line = line.strip()
333 if not line or line.startswith("#"):
334 continue
335
336 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
337 pieces = line.split()
338 if len(pieces) != 5:
339 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
340
341 # Ignore entries that are managed by vold.
342 options = pieces[4]
343 if "voldmanaged=" in options:
344 continue
345
346 # It's a good line, parse it.
347 length = 0
348 options = options.split(",")
349 for i in options:
350 if i.startswith("length="):
351 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800352 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800353 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700354 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800355
Tao Baod1de6f32017-03-01 16:38:48 -0800356 mount_flags = pieces[3]
357 # Honor the SELinux context if present.
358 context = None
359 for i in mount_flags.split(","):
360 if i.startswith("context="):
361 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800362
Tao Baod1de6f32017-03-01 16:38:48 -0800363 mount_point = pieces[1]
364 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
365 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800366
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700367 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700368 # system. Other areas assume system is always at "/system" so point /system
369 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700370 if system_root_image:
371 assert not d.has_key("/system") and d.has_key("/")
372 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700373 return d
374
375
Doug Zongker37974732010-09-16 17:44:38 -0700376def DumpInfoDict(d):
377 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800378 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700379
Dan Albert8b72aef2015-03-23 19:13:21 -0700380
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800381def AppendAVBSigningArgs(cmd, partition):
382 """Append signing arguments for avbtool."""
383 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
384 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
385 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
386 if key_path and algorithm:
387 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700388 avb_salt = OPTIONS.info_dict.get("avb_salt")
389 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
390 if avb_salt and partition != "vbmeta":
391 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800392
393
Tao Bao02a08592018-07-22 12:40:45 -0700394def GetAvbChainedPartitionArg(partition, info_dict, key=None):
395 """Constructs and returns the arg to build or verify a chained partition.
396
397 Args:
398 partition: The partition name.
399 info_dict: The info dict to look up the key info and rollback index
400 location.
401 key: The key to be used for building or verifying the partition. Defaults to
402 the key listed in info_dict.
403
404 Returns:
405 A string of form "partition:rollback_index_location:key" that can be used to
406 build or verify vbmeta image.
407
408 Raises:
409 AssertionError: When it fails to extract the public key with avbtool.
410 """
411 if key is None:
412 key = info_dict["avb_" + partition + "_key_path"]
413 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
414 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
415 proc = Run(
416 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path],
417 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
418 stdoutdata, _ = proc.communicate()
419 assert proc.returncode == 0, \
420 "Failed to extract pubkey for {}:\n{}".format(
421 partition, stdoutdata)
422
423 rollback_index_location = info_dict[
424 "avb_" + partition + "_rollback_index_location"]
425 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
426
427
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700428def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800429 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700430 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700431
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700432 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800433 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
434 we are building a two-step special image (i.e. building a recovery image to
435 be loaded into /boot in two-step OTAs).
436
437 Return the image data, or None if sourcedir does not appear to contains files
438 for building the requested image.
439 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700440
441 def make_ramdisk():
442 ramdisk_img = tempfile.NamedTemporaryFile()
443
444 if os.access(fs_config_file, os.F_OK):
445 cmd = ["mkbootfs", "-f", fs_config_file,
446 os.path.join(sourcedir, "RAMDISK")]
447 else:
448 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
449 p1 = Run(cmd, stdout=subprocess.PIPE)
450 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
451
452 p2.wait()
453 p1.wait()
454 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
455 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
456
457 return ramdisk_img
458
459 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
460 return None
461
462 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700463 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700464
Doug Zongkerd5131602012-08-02 14:46:42 -0700465 if info_dict is None:
466 info_dict = OPTIONS.info_dict
467
Doug Zongkereef39442009-04-02 12:14:19 -0700468 img = tempfile.NamedTemporaryFile()
469
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700470 if has_ramdisk:
471 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700472
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800473 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
474 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
475
476 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700477
Benoit Fradina45a8682014-07-14 21:00:43 +0200478 fn = os.path.join(sourcedir, "second")
479 if os.access(fn, os.F_OK):
480 cmd.append("--second")
481 cmd.append(fn)
482
Doug Zongker171f1cd2009-06-15 22:36:37 -0700483 fn = os.path.join(sourcedir, "cmdline")
484 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700485 cmd.append("--cmdline")
486 cmd.append(open(fn).read().rstrip("\n"))
487
488 fn = os.path.join(sourcedir, "base")
489 if os.access(fn, os.F_OK):
490 cmd.append("--base")
491 cmd.append(open(fn).read().rstrip("\n"))
492
Ying Wang4de6b5b2010-08-25 14:29:34 -0700493 fn = os.path.join(sourcedir, "pagesize")
494 if os.access(fn, os.F_OK):
495 cmd.append("--pagesize")
496 cmd.append(open(fn).read().rstrip("\n"))
497
Tao Bao76def242017-11-21 09:25:31 -0800498 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700499 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700500 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700501
Tao Bao76def242017-11-21 09:25:31 -0800502 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000503 if args and args.strip():
504 cmd.extend(shlex.split(args))
505
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700506 if has_ramdisk:
507 cmd.extend(["--ramdisk", ramdisk_img.name])
508
Tao Baod95e9fd2015-03-29 23:07:41 -0700509 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800510 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700511 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700512 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700513 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700514 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700515
Tao Baobf70c312017-07-11 17:27:55 -0700516 # "boot" or "recovery", without extension.
517 partition_name = os.path.basename(sourcedir).lower()
518
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700519 if (partition_name == "recovery" and
520 info_dict.get("include_recovery_dtbo") == "true"):
521 fn = os.path.join(sourcedir, "recovery_dtbo")
522 cmd.extend(["--recovery_dtbo", fn])
523
Doug Zongker38a649f2009-06-17 09:07:09 -0700524 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700525 p.communicate()
Tao Baobf70c312017-07-11 17:27:55 -0700526 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700527
Tao Bao76def242017-11-21 09:25:31 -0800528 if (info_dict.get("boot_signer") == "true" and
529 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800530 # Hard-code the path as "/boot" for two-step special recovery image (which
531 # will be loaded into /boot during the two-step OTA).
532 if two_step_image:
533 path = "/boot"
534 else:
Tao Baobf70c312017-07-11 17:27:55 -0700535 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700536 cmd = [OPTIONS.boot_signer_path]
537 cmd.extend(OPTIONS.boot_signer_args)
538 cmd.extend([path, img.name,
539 info_dict["verity_key"] + ".pk8",
540 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700541 p = Run(cmd, stdout=subprocess.PIPE)
542 p.communicate()
543 assert p.returncode == 0, "boot_signer of %s image failed" % path
544
Tao Baod95e9fd2015-03-29 23:07:41 -0700545 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800546 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700547 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700548 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800549 # We have switched from the prebuilt futility binary to using the tool
550 # (futility-host) built from the source. Override the setting in the old
551 # TF.zip.
552 futility = info_dict["futility"]
553 if futility.startswith("prebuilts/"):
554 futility = "futility-host"
555 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700556 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700557 info_dict["vboot_key"] + ".vbprivk",
558 info_dict["vboot_subkey"] + ".vbprivk",
559 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700560 img.name]
561 p = Run(cmd, stdout=subprocess.PIPE)
562 p.communicate()
563 assert p.returncode == 0, "vboot_signer of %s image failed" % path
564
Tao Baof3282b42015-04-01 11:21:55 -0700565 # Clean up the temp files.
566 img_unsigned.close()
567 img_keyblock.close()
568
David Zeuthen8fecb282017-12-01 16:24:01 -0500569 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800570 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700571 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500572 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400573 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700574 "--partition_size", str(part_size), "--partition_name",
575 partition_name]
576 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500577 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400578 if args and args.strip():
579 cmd.extend(shlex.split(args))
580 p = Run(cmd, stdout=subprocess.PIPE)
581 p.communicate()
582 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c312017-07-11 17:27:55 -0700583 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500584
585 img.seek(os.SEEK_SET, 0)
586 data = img.read()
587
588 if has_ramdisk:
589 ramdisk_img.close()
590 img.close()
591
592 return data
593
594
Doug Zongkerd5131602012-08-02 14:46:42 -0700595def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800596 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700597 """Return a File object with the desired bootable image.
598
599 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
600 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
601 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700602
Doug Zongker55d93282011-01-25 17:03:34 -0800603 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
604 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800605 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800606 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700607
608 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
609 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800610 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700611 return File.FromLocalFile(name, prebuilt_path)
612
Tao Bao89fbb0f2017-01-10 10:47:58 -0800613 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700614
615 if info_dict is None:
616 info_dict = OPTIONS.info_dict
617
618 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800619 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
620 # for recovery.
621 has_ramdisk = (info_dict.get("system_root_image") != "true" or
622 prebuilt_name != "boot.img" or
623 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700624
Doug Zongker6f1d0312014-08-22 08:07:12 -0700625 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400626 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
627 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800628 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700629 if data:
630 return File(name, data)
631 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800632
Doug Zongkereef39442009-04-02 12:14:19 -0700633
Narayan Kamatha07bf042017-08-14 14:49:21 +0100634def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800635 """Gunzips the given gzip compressed file to a given output file."""
636 with gzip.open(in_filename, "rb") as in_file, \
637 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100638 shutil.copyfileobj(in_file, out_file)
639
640
Doug Zongker75f17362009-12-08 13:46:44 -0800641def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800642 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800643
Tao Bao1c830bf2017-12-25 10:43:47 -0800644 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
645 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800646
Tao Bao1c830bf2017-12-25 10:43:47 -0800647 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800648 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800649 """
Doug Zongkereef39442009-04-02 12:14:19 -0700650
Doug Zongker55d93282011-01-25 17:03:34 -0800651 def unzip_to_dir(filename, dirname):
652 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
653 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800654 cmd.extend(pattern)
Tao Bao80921982018-03-21 21:02:19 -0700655 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
656 stdoutdata, _ = p.communicate()
Doug Zongker55d93282011-01-25 17:03:34 -0800657 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700658 raise ExternalError(
659 "Failed to unzip input target-files \"{}\":\n{}".format(
660 filename, stdoutdata))
Doug Zongker55d93282011-01-25 17:03:34 -0800661
Tao Bao1c830bf2017-12-25 10:43:47 -0800662 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800663 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
664 if m:
665 unzip_to_dir(m.group(1), tmp)
666 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
667 filename = m.group(1)
668 else:
669 unzip_to_dir(filename, tmp)
670
Tao Baodba59ee2018-01-09 13:21:02 -0800671 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700672
673
Tao Baoe709b092018-02-07 12:40:00 -0800674def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -0800675 """Returns a SparseImage object suitable for passing to BlockImageDiff.
676
677 This function loads the specified sparse image from the given path, and
678 performs additional processing for OTA purpose. For example, it always adds
679 block 0 to clobbered blocks list. It also detects files that cannot be
680 reconstructed from the block list, for whom we should avoid applying imgdiff.
681
682 Args:
683 which: The partition name, which must be "system" or "vendor".
684 tmpdir: The directory that contains the prebuilt image and block map file.
685 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800686 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -0800687
688 Returns:
689 A SparseImage object, with file_map info loaded.
690 """
691 assert which in ("system", "vendor")
692
693 path = os.path.join(tmpdir, "IMAGES", which + ".img")
694 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
695
696 # The image and map files must have been created prior to calling
697 # ota_from_target_files.py (since LMP).
698 assert os.path.exists(path) and os.path.exists(mappath)
699
700 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
701 # it to clobbered_blocks so that it will be written to the target
702 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
703 clobbered_blocks = "0"
704
Tao Baoe709b092018-02-07 12:40:00 -0800705 image = sparse_img.SparseImage(path, mappath, clobbered_blocks,
706 allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -0800707
708 # block.map may contain less blocks, because mke2fs may skip allocating blocks
709 # if they contain all zeros. We can't reconstruct such a file from its block
710 # list. Tag such entries accordingly. (Bug: 65213616)
711 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800712 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700713 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800714 continue
715
Tao Baod3554e62018-07-10 15:31:22 -0700716 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that when
717 # using system_root_image, the filename listed in system.map may contain an
718 # additional leading slash (i.e. "//system/framework/am.jar"). Using lstrip
719 # to get consistent results.
720 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
721
722 # Special handling another case with system_root_image, where files not
723 # under /system (e.g. "/sbin/charger") are packed under ROOT/ in a
724 # target_files.zip.
725 if which == 'system' and not arcname.startswith('SYSTEM'):
726 arcname = 'ROOT/' + arcname
727
728 assert arcname in input_zip.namelist(), \
729 "Failed to find the ZIP entry for {}".format(entry)
730
Tao Baoc765cca2018-01-31 17:32:40 -0800731 info = input_zip.getinfo(arcname)
732 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800733
734 # If a RangeSet has been tagged as using shared blocks while loading the
735 # image, its block list must be already incomplete due to that reason. Don't
736 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
737 if ranges.extra.get('uses_shared_blocks'):
738 continue
739
Tao Baoc765cca2018-01-31 17:32:40 -0800740 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
741 ranges.extra['incomplete'] = True
742
743 return image
744
745
Doug Zongkereef39442009-04-02 12:14:19 -0700746def GetKeyPasswords(keylist):
747 """Given a list of keys, prompt the user to enter passwords for
748 those which require them. Return a {key: password} dict. password
749 will be None if the key has no password."""
750
Doug Zongker8ce7c252009-05-22 13:34:54 -0700751 no_passwords = []
752 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700753 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700754 devnull = open("/dev/null", "w+b")
755 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800756 # We don't need a password for things that aren't really keys.
757 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700758 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700759 continue
760
T.R. Fullhart37e10522013-03-18 10:31:26 -0700761 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700762 "-inform", "DER", "-nocrypt"],
763 stdin=devnull.fileno(),
764 stdout=devnull.fileno(),
765 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700766 p.communicate()
767 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700768 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700769 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700770 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700771 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
772 "-inform", "DER", "-passin", "pass:"],
773 stdin=devnull.fileno(),
774 stdout=devnull.fileno(),
775 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700776 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700777 if p.returncode == 0:
778 # Encrypted key with empty string as password.
779 key_passwords[k] = ''
780 elif stderr.startswith('Error decrypting key'):
781 # Definitely encrypted key.
782 # It would have said "Error reading key" if it didn't parse correctly.
783 need_passwords.append(k)
784 else:
785 # Potentially, a type of key that openssl doesn't understand.
786 # We'll let the routines in signapk.jar handle it.
787 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700788 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700789
T.R. Fullhart37e10522013-03-18 10:31:26 -0700790 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800791 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700792 return key_passwords
793
794
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800795def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700796 """Gets the minSdkVersion declared in the APK.
797
798 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
799 This can be both a decimal number (API Level) or a codename.
800
801 Args:
802 apk_name: The APK filename.
803
804 Returns:
805 The parsed SDK version string.
806
807 Raises:
808 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800809 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700810 proc = Run(
811 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
812 stderr=subprocess.PIPE)
813 stdoutdata, stderrdata = proc.communicate()
814 if proc.returncode != 0:
815 raise ExternalError(
816 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
817 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800818
Tao Baof47bf0f2018-03-21 23:28:51 -0700819 for line in stdoutdata.split("\n"):
820 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800821 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
822 if m:
823 return m.group(1)
824 raise ExternalError("No minSdkVersion returned by aapt")
825
826
827def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700828 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800829
Tao Baof47bf0f2018-03-21 23:28:51 -0700830 If minSdkVersion is set to a codename, it is translated to a number using the
831 provided map.
832
833 Args:
834 apk_name: The APK filename.
835
836 Returns:
837 The parsed SDK version number.
838
839 Raises:
840 ExternalError: On failing to get the min SDK version number.
841 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800842 version = GetMinSdkVersion(apk_name)
843 try:
844 return int(version)
845 except ValueError:
846 # Not a decimal number. Codename?
847 if version in codename_to_api_level_map:
848 return codename_to_api_level_map[version]
849 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700850 raise ExternalError(
851 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
852 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800853
854
855def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800856 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700857 """Sign the input_name zip/jar/apk, producing output_name. Use the
858 given key and password (the latter may be None if the key does not
859 have a password.
860
Doug Zongker951495f2009-08-14 12:44:19 -0700861 If whole_file is true, use the "-w" option to SignApk to embed a
862 signature that covers the whole file in the archive comment of the
863 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800864
865 min_api_level is the API Level (int) of the oldest platform this file may end
866 up on. If not specified for an APK, the API Level is obtained by interpreting
867 the minSdkVersion attribute of the APK's AndroidManifest.xml.
868
869 codename_to_api_level_map is needed to translate the codename which may be
870 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700871 """
Tao Bao76def242017-11-21 09:25:31 -0800872 if codename_to_api_level_map is None:
873 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700874
Alex Klyubin9667b182015-12-10 13:38:50 -0800875 java_library_path = os.path.join(
876 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
877
Tao Baoe95540e2016-11-08 12:08:53 -0800878 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
879 ["-Djava.library.path=" + java_library_path,
880 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
881 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700882 if whole_file:
883 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800884
885 min_sdk_version = min_api_level
886 if min_sdk_version is None:
887 if not whole_file:
888 min_sdk_version = GetMinSdkVersionInt(
889 input_name, codename_to_api_level_map)
890 if min_sdk_version is not None:
891 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
892
T.R. Fullhart37e10522013-03-18 10:31:26 -0700893 cmd.extend([key + OPTIONS.public_key_suffix,
894 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800895 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700896
Tao Bao80921982018-03-21 21:02:19 -0700897 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
898 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700899 if password is not None:
900 password += "\n"
Tao Bao80921982018-03-21 21:02:19 -0700901 stdoutdata, _ = p.communicate(password)
Doug Zongkereef39442009-04-02 12:14:19 -0700902 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700903 raise ExternalError(
904 "Failed to run signapk.jar: return code {}:\n{}".format(
905 p.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700906
Doug Zongkereef39442009-04-02 12:14:19 -0700907
Doug Zongker37974732010-09-16 17:44:38 -0700908def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800909 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700910
Tao Bao9dd909e2017-11-14 11:27:32 -0800911 For non-AVB images, raise exception if the data is too big. Print a warning
912 if the data is nearing the maximum size.
913
914 For AVB images, the actual image size should be identical to the limit.
915
916 Args:
917 data: A string that contains all the data for the partition.
918 target: The partition name. The ".img" suffix is optional.
919 info_dict: The dict to be looked up for relevant info.
920 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700921 if target.endswith(".img"):
922 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700923 mount_point = "/" + target
924
Ying Wangf8824af2014-06-03 14:07:27 -0700925 fs_type = None
926 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700927 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700928 if mount_point == "/userdata":
929 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700930 p = info_dict["fstab"][mount_point]
931 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800932 device = p.device
933 if "/" in device:
934 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -0800935 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -0700936 if not fs_type or not limit:
937 return
Doug Zongkereef39442009-04-02 12:14:19 -0700938
Andrew Boie0f9aec82012-02-14 09:32:52 -0800939 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800940 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
941 # path.
942 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
943 if size != limit:
944 raise ExternalError(
945 "Mismatching image size for %s: expected %d actual %d" % (
946 target, limit, size))
947 else:
948 pct = float(size) * 100.0 / limit
949 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
950 if pct >= 99.0:
951 raise ExternalError(msg)
952 elif pct >= 95.0:
953 print("\n WARNING: %s\n" % (msg,))
954 elif OPTIONS.verbose:
955 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700956
957
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800958def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800959 """Parses the APK certs info from a given target-files zip.
960
961 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
962 tuple with the following elements: (1) a dictionary that maps packages to
963 certs (based on the "certificate" and "private_key" attributes in the file;
964 (2) a string representing the extension of compressed APKs in the target files
965 (e.g ".gz", ".bro").
966
967 Args:
968 tf_zip: The input target_files ZipFile (already open).
969
970 Returns:
971 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
972 the extension string of compressed APKs (e.g. ".gz"), or None if there's
973 no compressed APKs.
974 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800975 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100976 compressed_extension = None
977
Tao Bao0f990332017-09-08 19:02:54 -0700978 # META/apkcerts.txt contains the info for _all_ the packages known at build
979 # time. Filter out the ones that are not installed.
980 installed_files = set()
981 for name in tf_zip.namelist():
982 basename = os.path.basename(name)
983 if basename:
984 installed_files.add(basename)
985
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800986 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
987 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700988 if not line:
989 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800990 m = re.match(
991 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
992 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
993 line)
994 if not m:
995 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100996
Tao Bao818ddf52018-01-05 11:17:34 -0800997 matches = m.groupdict()
998 cert = matches["CERT"]
999 privkey = matches["PRIVKEY"]
1000 name = matches["NAME"]
1001 this_compressed_extension = matches["COMPRESSED"]
1002
1003 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1004 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1005 if cert in SPECIAL_CERT_STRINGS and not privkey:
1006 certmap[name] = cert
1007 elif (cert.endswith(OPTIONS.public_key_suffix) and
1008 privkey.endswith(OPTIONS.private_key_suffix) and
1009 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1010 certmap[name] = cert[:-public_key_suffix_len]
1011 else:
1012 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1013
1014 if not this_compressed_extension:
1015 continue
1016
1017 # Only count the installed files.
1018 filename = name + '.' + this_compressed_extension
1019 if filename not in installed_files:
1020 continue
1021
1022 # Make sure that all the values in the compression map have the same
1023 # extension. We don't support multiple compression methods in the same
1024 # system image.
1025 if compressed_extension:
1026 if this_compressed_extension != compressed_extension:
1027 raise ValueError(
1028 "Multiple compressed extensions: {} vs {}".format(
1029 compressed_extension, this_compressed_extension))
1030 else:
1031 compressed_extension = this_compressed_extension
1032
1033 return (certmap,
1034 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001035
1036
Doug Zongkereef39442009-04-02 12:14:19 -07001037COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001038Global options
1039
1040 -p (--path) <dir>
1041 Prepend <dir>/bin to the list of places to search for binaries run by this
1042 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001043
Doug Zongker05d3dea2009-06-22 11:32:31 -07001044 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001045 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001046
Tao Bao30df8b42018-04-23 15:32:53 -07001047 -x (--extra) <key=value>
1048 Add a key/value pair to the 'extras' dict, which device-specific extension
1049 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001050
Doug Zongkereef39442009-04-02 12:14:19 -07001051 -v (--verbose)
1052 Show command lines being executed.
1053
1054 -h (--help)
1055 Display this usage message and exit.
1056"""
1057
1058def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001059 print(docstring.rstrip("\n"))
1060 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001061
1062
1063def ParseOptions(argv,
1064 docstring,
1065 extra_opts="", extra_long_opts=(),
1066 extra_option_handler=None):
1067 """Parse the options in argv and return any arguments that aren't
1068 flags. docstring is the calling module's docstring, to be displayed
1069 for errors and -h. extra_opts and extra_long_opts are for flags
1070 defined by the caller, which are processed by passing them to
1071 extra_option_handler."""
1072
1073 try:
1074 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001075 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001076 ["help", "verbose", "path=", "signapk_path=",
1077 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001078 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001079 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1080 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001081 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001082 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001083 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001084 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001085 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001086 sys.exit(2)
1087
Doug Zongkereef39442009-04-02 12:14:19 -07001088 for o, a in opts:
1089 if o in ("-h", "--help"):
1090 Usage(docstring)
1091 sys.exit()
1092 elif o in ("-v", "--verbose"):
1093 OPTIONS.verbose = True
1094 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001095 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001096 elif o in ("--signapk_path",):
1097 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001098 elif o in ("--signapk_shared_library_path",):
1099 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001100 elif o in ("--extra_signapk_args",):
1101 OPTIONS.extra_signapk_args = shlex.split(a)
1102 elif o in ("--java_path",):
1103 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001104 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001105 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001106 elif o in ("--public_key_suffix",):
1107 OPTIONS.public_key_suffix = a
1108 elif o in ("--private_key_suffix",):
1109 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001110 elif o in ("--boot_signer_path",):
1111 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001112 elif o in ("--boot_signer_args",):
1113 OPTIONS.boot_signer_args = shlex.split(a)
1114 elif o in ("--verity_signer_path",):
1115 OPTIONS.verity_signer_path = a
1116 elif o in ("--verity_signer_args",):
1117 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001118 elif o in ("-s", "--device_specific"):
1119 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001120 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001121 key, value = a.split("=", 1)
1122 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001123 else:
1124 if extra_option_handler is None or not extra_option_handler(o, a):
1125 assert False, "unknown option \"%s\"" % (o,)
1126
Doug Zongker85448772014-09-09 14:59:20 -07001127 if OPTIONS.search_path:
1128 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1129 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001130
1131 return args
1132
1133
Tao Bao4c851b12016-09-19 13:54:38 -07001134def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001135 """Make a temp file and add it to the list of things to be deleted
1136 when Cleanup() is called. Return the filename."""
1137 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1138 os.close(fd)
1139 OPTIONS.tempfiles.append(fn)
1140 return fn
1141
1142
Tao Bao1c830bf2017-12-25 10:43:47 -08001143def MakeTempDir(prefix='tmp', suffix=''):
1144 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1145
1146 Returns:
1147 The absolute pathname of the new directory.
1148 """
1149 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1150 OPTIONS.tempfiles.append(dir_name)
1151 return dir_name
1152
1153
Doug Zongkereef39442009-04-02 12:14:19 -07001154def Cleanup():
1155 for i in OPTIONS.tempfiles:
1156 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001157 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001158 else:
1159 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001160 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001161
1162
1163class PasswordManager(object):
1164 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001165 self.editor = os.getenv("EDITOR")
1166 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001167
1168 def GetPasswords(self, items):
1169 """Get passwords corresponding to each string in 'items',
1170 returning a dict. (The dict may have keys in addition to the
1171 values in 'items'.)
1172
1173 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1174 user edit that file to add more needed passwords. If no editor is
1175 available, or $ANDROID_PW_FILE isn't define, prompts the user
1176 interactively in the ordinary way.
1177 """
1178
1179 current = self.ReadFile()
1180
1181 first = True
1182 while True:
1183 missing = []
1184 for i in items:
1185 if i not in current or not current[i]:
1186 missing.append(i)
1187 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001188 if not missing:
1189 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001190
1191 for i in missing:
1192 current[i] = ""
1193
1194 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001195 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001196 answer = raw_input("try to edit again? [y]> ").strip()
1197 if answer and answer[0] not in 'yY':
1198 raise RuntimeError("key passwords unavailable")
1199 first = False
1200
1201 current = self.UpdateAndReadFile(current)
1202
Dan Albert8b72aef2015-03-23 19:13:21 -07001203 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001204 """Prompt the user to enter a value (password) for each key in
1205 'current' whose value is fales. Returns a new dict with all the
1206 values.
1207 """
1208 result = {}
1209 for k, v in sorted(current.iteritems()):
1210 if v:
1211 result[k] = v
1212 else:
1213 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001214 result[k] = getpass.getpass(
1215 "Enter password for %s key> " % k).strip()
1216 if result[k]:
1217 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001218 return result
1219
1220 def UpdateAndReadFile(self, current):
1221 if not self.editor or not self.pwfile:
1222 return self.PromptResult(current)
1223
1224 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001225 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001226 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1227 f.write("# (Additional spaces are harmless.)\n\n")
1228
1229 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001230 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1231 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001232 f.write("[[[ %s ]]] %s\n" % (v, k))
1233 if not v and first_line is None:
1234 # position cursor on first line with no password.
1235 first_line = i + 4
1236 f.close()
1237
1238 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1239 _, _ = p.communicate()
1240
1241 return self.ReadFile()
1242
1243 def ReadFile(self):
1244 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001245 if self.pwfile is None:
1246 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001247 try:
1248 f = open(self.pwfile, "r")
1249 for line in f:
1250 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001251 if not line or line[0] == '#':
1252 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001253 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1254 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001255 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001256 else:
1257 result[m.group(2)] = m.group(1)
1258 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001259 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001260 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001261 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001262 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001263
1264
Dan Albert8e0178d2015-01-27 15:53:15 -08001265def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1266 compress_type=None):
1267 import datetime
1268
1269 # http://b/18015246
1270 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1271 # for files larger than 2GiB. We can work around this by adjusting their
1272 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1273 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1274 # it isn't clear to me exactly what circumstances cause this).
1275 # `zipfile.write()` must be used directly to work around this.
1276 #
1277 # This mess can be avoided if we port to python3.
1278 saved_zip64_limit = zipfile.ZIP64_LIMIT
1279 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1280
1281 if compress_type is None:
1282 compress_type = zip_file.compression
1283 if arcname is None:
1284 arcname = filename
1285
1286 saved_stat = os.stat(filename)
1287
1288 try:
1289 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1290 # file to be zipped and reset it when we're done.
1291 os.chmod(filename, perms)
1292
1293 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001294 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1295 # intentional. zip stores datetimes in local time without a time zone
1296 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1297 # in the zip archive.
1298 local_epoch = datetime.datetime.fromtimestamp(0)
1299 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001300 os.utime(filename, (timestamp, timestamp))
1301
1302 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1303 finally:
1304 os.chmod(filename, saved_stat.st_mode)
1305 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1306 zipfile.ZIP64_LIMIT = saved_zip64_limit
1307
1308
Tao Bao58c1b962015-05-20 09:32:18 -07001309def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001310 compress_type=None):
1311 """Wrap zipfile.writestr() function to work around the zip64 limit.
1312
1313 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1314 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1315 when calling crc32(bytes).
1316
1317 But it still works fine to write a shorter string into a large zip file.
1318 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1319 when we know the string won't be too long.
1320 """
1321
1322 saved_zip64_limit = zipfile.ZIP64_LIMIT
1323 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1324
1325 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1326 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001327 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001328 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001329 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001330 else:
Tao Baof3282b42015-04-01 11:21:55 -07001331 zinfo = zinfo_or_arcname
1332
1333 # If compress_type is given, it overrides the value in zinfo.
1334 if compress_type is not None:
1335 zinfo.compress_type = compress_type
1336
Tao Bao58c1b962015-05-20 09:32:18 -07001337 # If perms is given, it has a priority.
1338 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001339 # If perms doesn't set the file type, mark it as a regular file.
1340 if perms & 0o770000 == 0:
1341 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001342 zinfo.external_attr = perms << 16
1343
Tao Baof3282b42015-04-01 11:21:55 -07001344 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001345 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1346
Dan Albert8b72aef2015-03-23 19:13:21 -07001347 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001348 zipfile.ZIP64_LIMIT = saved_zip64_limit
1349
1350
Tao Bao89d7ab22017-12-14 17:05:33 -08001351def ZipDelete(zip_filename, entries):
1352 """Deletes entries from a ZIP file.
1353
1354 Since deleting entries from a ZIP file is not supported, it shells out to
1355 'zip -d'.
1356
1357 Args:
1358 zip_filename: The name of the ZIP file.
1359 entries: The name of the entry, or the list of names to be deleted.
1360
1361 Raises:
1362 AssertionError: In case of non-zero return from 'zip'.
1363 """
1364 if isinstance(entries, basestring):
1365 entries = [entries]
1366 cmd = ["zip", "-d", zip_filename] + entries
1367 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1368 stdoutdata, _ = proc.communicate()
1369 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1370 stdoutdata)
1371
1372
Tao Baof3282b42015-04-01 11:21:55 -07001373def ZipClose(zip_file):
1374 # http://b/18015246
1375 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1376 # central directory.
1377 saved_zip64_limit = zipfile.ZIP64_LIMIT
1378 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1379
1380 zip_file.close()
1381
1382 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001383
1384
1385class DeviceSpecificParams(object):
1386 module = None
1387 def __init__(self, **kwargs):
1388 """Keyword arguments to the constructor become attributes of this
1389 object, which is passed to all functions in the device-specific
1390 module."""
1391 for k, v in kwargs.iteritems():
1392 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001393 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001394
1395 if self.module is None:
1396 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001397 if not path:
1398 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001399 try:
1400 if os.path.isdir(path):
1401 info = imp.find_module("releasetools", [path])
1402 else:
1403 d, f = os.path.split(path)
1404 b, x = os.path.splitext(f)
1405 if x == ".py":
1406 f = b
1407 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001408 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001409 self.module = imp.load_module("device_specific", *info)
1410 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001411 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001412
1413 def _DoCall(self, function_name, *args, **kwargs):
1414 """Call the named function in the device-specific module, passing
1415 the given args and kwargs. The first argument to the call will be
1416 the DeviceSpecific object itself. If there is no module, or the
1417 module does not define the function, return the value of the
1418 'default' kwarg (which itself defaults to None)."""
1419 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001420 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001421 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1422
1423 def FullOTA_Assertions(self):
1424 """Called after emitting the block of assertions at the top of a
1425 full OTA package. Implementations can add whatever additional
1426 assertions they like."""
1427 return self._DoCall("FullOTA_Assertions")
1428
Doug Zongkere5ff5902012-01-17 10:55:37 -08001429 def FullOTA_InstallBegin(self):
1430 """Called at the start of full OTA installation."""
1431 return self._DoCall("FullOTA_InstallBegin")
1432
Doug Zongker05d3dea2009-06-22 11:32:31 -07001433 def FullOTA_InstallEnd(self):
1434 """Called at the end of full OTA installation; typically this is
1435 used to install the image for the device's baseband processor."""
1436 return self._DoCall("FullOTA_InstallEnd")
1437
1438 def IncrementalOTA_Assertions(self):
1439 """Called after emitting the block of assertions at the top of an
1440 incremental OTA package. Implementations can add whatever
1441 additional assertions they like."""
1442 return self._DoCall("IncrementalOTA_Assertions")
1443
Doug Zongkere5ff5902012-01-17 10:55:37 -08001444 def IncrementalOTA_VerifyBegin(self):
1445 """Called at the start of the verification phase of incremental
1446 OTA installation; additional checks can be placed here to abort
1447 the script before any changes are made."""
1448 return self._DoCall("IncrementalOTA_VerifyBegin")
1449
Doug Zongker05d3dea2009-06-22 11:32:31 -07001450 def IncrementalOTA_VerifyEnd(self):
1451 """Called at the end of the verification phase of incremental OTA
1452 installation; additional checks can be placed here to abort the
1453 script before any changes are made."""
1454 return self._DoCall("IncrementalOTA_VerifyEnd")
1455
Doug Zongkere5ff5902012-01-17 10:55:37 -08001456 def IncrementalOTA_InstallBegin(self):
1457 """Called at the start of incremental OTA installation (after
1458 verification is complete)."""
1459 return self._DoCall("IncrementalOTA_InstallBegin")
1460
Doug Zongker05d3dea2009-06-22 11:32:31 -07001461 def IncrementalOTA_InstallEnd(self):
1462 """Called at the end of incremental OTA installation; typically
1463 this is used to install the image for the device's baseband
1464 processor."""
1465 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001466
Tao Bao9bc6bb22015-11-09 16:58:28 -08001467 def VerifyOTA_Assertions(self):
1468 return self._DoCall("VerifyOTA_Assertions")
1469
Tao Bao76def242017-11-21 09:25:31 -08001470
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001471class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001472 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001473 self.name = name
1474 self.data = data
1475 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001476 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001477 self.sha1 = sha1(data).hexdigest()
1478
1479 @classmethod
1480 def FromLocalFile(cls, name, diskname):
1481 f = open(diskname, "rb")
1482 data = f.read()
1483 f.close()
1484 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001485
1486 def WriteToTemp(self):
1487 t = tempfile.NamedTemporaryFile()
1488 t.write(self.data)
1489 t.flush()
1490 return t
1491
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001492 def WriteToDir(self, d):
1493 with open(os.path.join(d, self.name), "wb") as fp:
1494 fp.write(self.data)
1495
Geremy Condra36bd3652014-02-06 19:45:10 -08001496 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001497 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001498
Tao Bao76def242017-11-21 09:25:31 -08001499
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001500DIFF_PROGRAM_BY_EXT = {
1501 ".gz" : "imgdiff",
1502 ".zip" : ["imgdiff", "-z"],
1503 ".jar" : ["imgdiff", "-z"],
1504 ".apk" : ["imgdiff", "-z"],
1505 ".img" : "imgdiff",
1506 }
1507
Tao Bao76def242017-11-21 09:25:31 -08001508
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001509class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001510 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001511 self.tf = tf
1512 self.sf = sf
1513 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001514 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001515
1516 def ComputePatch(self):
1517 """Compute the patch (as a string of data) needed to turn sf into
1518 tf. Returns the same tuple as GetPatch()."""
1519
1520 tf = self.tf
1521 sf = self.sf
1522
Doug Zongker24cd2802012-08-14 16:36:15 -07001523 if self.diff_program:
1524 diff_program = self.diff_program
1525 else:
1526 ext = os.path.splitext(tf.name)[1]
1527 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001528
1529 ttemp = tf.WriteToTemp()
1530 stemp = sf.WriteToTemp()
1531
1532 ext = os.path.splitext(tf.name)[1]
1533
1534 try:
1535 ptemp = tempfile.NamedTemporaryFile()
1536 if isinstance(diff_program, list):
1537 cmd = copy.copy(diff_program)
1538 else:
1539 cmd = [diff_program]
1540 cmd.append(stemp.name)
1541 cmd.append(ttemp.name)
1542 cmd.append(ptemp.name)
1543 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001544 err = []
1545 def run():
1546 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001547 if e:
1548 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001549 th = threading.Thread(target=run)
1550 th.start()
1551 th.join(timeout=300) # 5 mins
1552 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001553 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001554 p.terminate()
1555 th.join(5)
1556 if th.is_alive():
1557 p.kill()
1558 th.join()
1559
Tianjie Xua2a9f992018-01-05 15:15:54 -08001560 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001561 print("WARNING: failure running %s:\n%s\n" % (
1562 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001563 self.patch = None
1564 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001565 diff = ptemp.read()
1566 finally:
1567 ptemp.close()
1568 stemp.close()
1569 ttemp.close()
1570
1571 self.patch = diff
1572 return self.tf, self.sf, self.patch
1573
1574
1575 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001576 """Returns a tuple of (target_file, source_file, patch_data).
1577
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001578 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001579 computing the patch failed.
1580 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001581 return self.tf, self.sf, self.patch
1582
1583
1584def ComputeDifferences(diffs):
1585 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001586 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001587
1588 # Do the largest files first, to try and reduce the long-pole effect.
1589 by_size = [(i.tf.size, i) for i in diffs]
1590 by_size.sort(reverse=True)
1591 by_size = [i[1] for i in by_size]
1592
1593 lock = threading.Lock()
1594 diff_iter = iter(by_size) # accessed under lock
1595
1596 def worker():
1597 try:
1598 lock.acquire()
1599 for d in diff_iter:
1600 lock.release()
1601 start = time.time()
1602 d.ComputePatch()
1603 dur = time.time() - start
1604 lock.acquire()
1605
1606 tf, sf, patch = d.GetPatch()
1607 if sf.name == tf.name:
1608 name = tf.name
1609 else:
1610 name = "%s (%s)" % (tf.name, sf.name)
1611 if patch is None:
Tao Bao76def242017-11-21 09:25:31 -08001612 print(
1613 "patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001614 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001615 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1616 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001617 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001618 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001619 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001620 raise
1621
1622 # start worker threads; wait for them all to finish.
1623 threads = [threading.Thread(target=worker)
1624 for i in range(OPTIONS.worker_threads)]
1625 for th in threads:
1626 th.start()
1627 while threads:
1628 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001629
1630
Dan Albert8b72aef2015-03-23 19:13:21 -07001631class BlockDifference(object):
1632 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001633 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001634 self.tgt = tgt
1635 self.src = src
1636 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001637 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001638 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001639
Tao Baodd2a5892015-03-12 12:32:37 -07001640 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001641 version = max(
1642 int(i) for i in
1643 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001644 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001645 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001646
1647 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001648 version=self.version,
1649 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001650 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001651 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001652 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001653 self.touched_src_ranges = b.touched_src_ranges
1654 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001655
Tao Baoaac4ad52015-10-16 15:26:34 -07001656 if src is None:
1657 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1658 else:
1659 _, self.device = GetTypeAndDevice("/" + partition,
1660 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001661
Tao Baod8d14be2016-02-04 14:26:02 -08001662 @property
1663 def required_cache(self):
1664 return self._required_cache
1665
Tao Bao76def242017-11-21 09:25:31 -08001666 def WriteScript(self, script, output_zip, progress=None,
1667 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001668 if not self.src:
1669 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001670 script.Print("Patching %s image unconditionally..." % (self.partition,))
1671 else:
1672 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001673
Dan Albert8b72aef2015-03-23 19:13:21 -07001674 if progress:
1675 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001676 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001677
1678 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001679 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001680
Tao Bao9bc6bb22015-11-09 16:58:28 -08001681 def WriteStrictVerifyScript(self, script):
1682 """Verify all the blocks in the care_map, including clobbered blocks.
1683
1684 This differs from the WriteVerifyScript() function: a) it prints different
1685 error messages; b) it doesn't allow half-way updated images to pass the
1686 verification."""
1687
1688 partition = self.partition
1689 script.Print("Verifying %s..." % (partition,))
1690 ranges = self.tgt.care_map
1691 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001692 script.AppendExtra(
1693 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1694 'ui_print("\\"%s\\" has unexpected contents.");' % (
1695 self.device, ranges_str,
1696 self.tgt.TotalSha1(include_clobbered_blocks=True),
1697 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001698 script.AppendExtra("")
1699
Tao Baod522bdc2016-04-12 15:53:16 -07001700 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001701 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001702
1703 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001704 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001705 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001706
1707 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001708 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001709 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001710 ranges = self.touched_src_ranges
1711 expected_sha1 = self.touched_src_sha1
1712 else:
1713 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1714 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001715
1716 # No blocks to be checked, skipping.
1717 if not ranges:
1718 return
1719
Tao Bao5ece99d2015-05-12 11:42:31 -07001720 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001721 script.AppendExtra(
1722 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1723 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1724 '"%s.patch.dat")) then' % (
1725 self.device, ranges_str, expected_sha1,
1726 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001727 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001728 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001729
Tianjie Xufc3422a2015-12-15 11:53:59 -08001730 if self.version >= 4:
1731
1732 # Bug: 21124327
1733 # When generating incrementals for the system and vendor partitions in
1734 # version 4 or newer, explicitly check the first block (which contains
1735 # the superblock) of the partition to see if it's what we expect. If
1736 # this check fails, give an explicit log message about the partition
1737 # having been remounted R/W (the most likely explanation).
1738 if self.check_first_block:
1739 script.AppendExtra('check_first_block("%s");' % (self.device,))
1740
1741 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001742 if partition == "system":
1743 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1744 else:
1745 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001746 script.AppendExtra((
1747 'ifelse (block_image_recover("{device}", "{ranges}") && '
1748 'block_image_verify("{device}", '
1749 'package_extract_file("{partition}.transfer.list"), '
1750 '"{partition}.new.dat", "{partition}.patch.dat"), '
1751 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001752 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001753 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001754 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001755
Tao Baodd2a5892015-03-12 12:32:37 -07001756 # Abort the OTA update. Note that the incremental OTA cannot be applied
1757 # even if it may match the checksum of the target partition.
1758 # a) If version < 3, operations like move and erase will make changes
1759 # unconditionally and damage the partition.
1760 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001761 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001762 if partition == "system":
1763 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1764 else:
1765 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1766 script.AppendExtra((
1767 'abort("E%d: %s partition has unexpected contents");\n'
1768 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001769
Tao Bao5fcaaef2015-06-01 13:40:49 -07001770 def _WritePostInstallVerifyScript(self, script):
1771 partition = self.partition
1772 script.Print('Verifying the updated %s image...' % (partition,))
1773 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1774 ranges = self.tgt.care_map
1775 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001776 script.AppendExtra(
1777 'if range_sha1("%s", "%s") == "%s" then' % (
1778 self.device, ranges_str,
1779 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001780
1781 # Bug: 20881595
1782 # Verify that extended blocks are really zeroed out.
1783 if self.tgt.extended:
1784 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001785 script.AppendExtra(
1786 'if range_sha1("%s", "%s") == "%s" then' % (
1787 self.device, ranges_str,
1788 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001789 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001790 if partition == "system":
1791 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1792 else:
1793 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001794 script.AppendExtra(
1795 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001796 ' abort("E%d: %s partition has unexpected non-zero contents after '
1797 'OTA update");\n'
1798 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001799 else:
1800 script.Print('Verified the updated %s image.' % (partition,))
1801
Tianjie Xu209db462016-05-24 17:34:52 -07001802 if partition == "system":
1803 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1804 else:
1805 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1806
Tao Bao5fcaaef2015-06-01 13:40:49 -07001807 script.AppendExtra(
1808 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001809 ' abort("E%d: %s partition has unexpected contents after OTA '
1810 'update");\n'
1811 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001812
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001813 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001814 ZipWrite(output_zip,
1815 '{}.transfer.list'.format(self.path),
1816 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001817
Tao Bao76def242017-11-21 09:25:31 -08001818 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1819 # its size. Quailty 9 almost triples the compression time but doesn't
1820 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001821 # zip | brotli(quality 6) | brotli(quality 9)
1822 # compressed_size: 942M | 869M (~8% reduced) | 854M
1823 # compression_time: 75s | 265s | 719s
1824 # decompression_time: 15s | 25s | 25s
1825
1826 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001827 brotli_cmd = ['brotli', '--quality=6',
1828 '--output={}.new.dat.br'.format(self.path),
1829 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001830 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao80921982018-03-21 21:02:19 -07001831 p = Run(brotli_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1832 stdoutdata, _ = p.communicate()
1833 assert p.returncode == 0, \
1834 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1835 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001836
1837 new_data_name = '{}.new.dat.br'.format(self.partition)
1838 ZipWrite(output_zip,
1839 '{}.new.dat.br'.format(self.path),
1840 new_data_name,
1841 compress_type=zipfile.ZIP_STORED)
1842 else:
1843 new_data_name = '{}.new.dat'.format(self.partition)
1844 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1845
Dan Albert8e0178d2015-01-27 15:53:15 -08001846 ZipWrite(output_zip,
1847 '{}.patch.dat'.format(self.path),
1848 '{}.patch.dat'.format(self.partition),
1849 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001850
Tianjie Xu209db462016-05-24 17:34:52 -07001851 if self.partition == "system":
1852 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1853 else:
1854 code = ErrorCode.VENDOR_UPDATE_FAILURE
1855
Dan Albert8e0178d2015-01-27 15:53:15 -08001856 call = ('block_image_update("{device}", '
1857 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001858 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001859 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001860 device=self.device, partition=self.partition,
1861 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001862 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001863
Dan Albert8b72aef2015-03-23 19:13:21 -07001864 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001865 data = source.ReadRangeSet(ranges)
1866 ctx = sha1()
1867
1868 for p in data:
1869 ctx.update(p)
1870
1871 return ctx.hexdigest()
1872
Tao Baoe9b61912015-07-09 17:37:49 -07001873 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1874 """Return the hash value for all zero blocks."""
1875 zero_block = '\x00' * 4096
1876 ctx = sha1()
1877 for _ in range(num_blocks):
1878 ctx.update(zero_block)
1879
1880 return ctx.hexdigest()
1881
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001882
1883DataImage = blockimgdiff.DataImage
1884
Tao Bao76def242017-11-21 09:25:31 -08001885
Doug Zongker96a57e72010-09-26 14:57:41 -07001886# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001887PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001888 "ext4": "EMMC",
1889 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001890 "f2fs": "EMMC",
1891 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001892}
Doug Zongker96a57e72010-09-26 14:57:41 -07001893
Tao Bao76def242017-11-21 09:25:31 -08001894
Doug Zongker96a57e72010-09-26 14:57:41 -07001895def GetTypeAndDevice(mount_point, info):
1896 fstab = info["fstab"]
1897 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001898 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1899 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001900 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001901 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001902
1903
1904def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001905 """Parses and converts a PEM-encoded certificate into DER-encoded.
1906
1907 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1908
1909 Returns:
1910 The decoded certificate string.
1911 """
1912 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001913 save = False
1914 for line in data.split("\n"):
1915 if "--END CERTIFICATE--" in line:
1916 break
1917 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001918 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001919 if "--BEGIN CERTIFICATE--" in line:
1920 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001921 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001922 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001923
Tao Bao04e1f012018-02-04 12:13:35 -08001924
1925def ExtractPublicKey(cert):
1926 """Extracts the public key (PEM-encoded) from the given certificate file.
1927
1928 Args:
1929 cert: The certificate filename.
1930
1931 Returns:
1932 The public key string.
1933
1934 Raises:
1935 AssertionError: On non-zero return from 'openssl'.
1936 """
1937 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1938 # While openssl 1.1 writes the key into the given filename followed by '-out',
1939 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1940 # stdout instead.
1941 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1942 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1943 pubkey, stderrdata = proc.communicate()
1944 assert proc.returncode == 0, \
1945 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1946 return pubkey
1947
1948
Doug Zongker412c02f2014-02-13 10:58:24 -08001949def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1950 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001951 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001952
Tao Bao6d5d6232018-03-09 17:04:42 -08001953 Most of the space in the boot and recovery images is just the kernel, which is
1954 identical for the two, so the resulting patch should be efficient. Add it to
1955 the output zip, along with a shell script that is run from init.rc on first
1956 boot to actually do the patching and install the new recovery image.
1957
1958 Args:
1959 input_dir: The top-level input directory of the target-files.zip.
1960 output_sink: The callback function that writes the result.
1961 recovery_img: File object for the recovery image.
1962 boot_img: File objects for the boot image.
1963 info_dict: A dict returned by common.LoadInfoDict() on the input
1964 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001965 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001966 if info_dict is None:
1967 info_dict = OPTIONS.info_dict
1968
Tao Bao6d5d6232018-03-09 17:04:42 -08001969 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001970
Tao Baof2cffbd2015-07-22 12:33:18 -07001971 if full_recovery_image:
1972 output_sink("etc/recovery.img", recovery_img.data)
1973
1974 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08001975 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07001976 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08001977 # With system-root-image, boot and recovery images will have mismatching
1978 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
1979 # to handle such a case.
1980 if system_root_image:
1981 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07001982 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08001983 assert not os.path.exists(path)
1984 else:
1985 diff_program = ["imgdiff"]
1986 if os.path.exists(path):
1987 diff_program.append("-b")
1988 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07001989 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08001990 else:
1991 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07001992
1993 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1994 _, _, patch = d.ComputePatch()
1995 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001996
Dan Albertebb19aa2015-03-27 19:11:53 -07001997 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001998 # The following GetTypeAndDevice()s need to use the path in the target
1999 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002000 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2001 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2002 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002003 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002004
Tao Baof2cffbd2015-07-22 12:33:18 -07002005 if full_recovery_image:
2006 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002007if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2008 applypatch \\
2009 --flash /system/etc/recovery.img \\
2010 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2011 log -t recovery "Installing new recovery image: succeeded" || \\
2012 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002013else
2014 log -t recovery "Recovery image already installed"
2015fi
2016""" % {'type': recovery_type,
2017 'device': recovery_device,
2018 'sha1': recovery_img.sha1,
2019 'size': recovery_img.size}
2020 else:
2021 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002022if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2023 applypatch %(bonus_args)s \\
2024 --patch /system/recovery-from-boot.p \\
2025 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2026 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2027 log -t recovery "Installing new recovery image: succeeded" || \\
2028 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002029else
2030 log -t recovery "Recovery image already installed"
2031fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002032""" % {'boot_size': boot_img.size,
2033 'boot_sha1': boot_img.sha1,
2034 'recovery_size': recovery_img.size,
2035 'recovery_sha1': recovery_img.sha1,
2036 'boot_type': boot_type,
2037 'boot_device': boot_device,
2038 'recovery_type': recovery_type,
2039 'recovery_device': recovery_device,
2040 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002041
2042 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002043 # in the L release.
2044 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002045
Tao Bao89fbb0f2017-01-10 10:47:58 -08002046 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002047
2048 output_sink(sh_location, sh)