blob: 227d7011a942c034c3c0edf1e1962e17d3f91a63 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Doug Zongkerea5d7a92010-09-12 15:26:16 -070017import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070018import errno
Doug Zongkereef39442009-04-02 12:14:19 -070019import getopt
20import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010021import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070022import imp
Doug Zongkereef39442009-04-02 12:14:19 -070023import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080024import platform
Doug Zongkereef39442009-04-02 12:14:19 -070025import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070026import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070027import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080028import string
Doug Zongkereef39442009-04-02 12:14:19 -070029import subprocess
30import sys
31import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070032import threading
33import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070034import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080035from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070036
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070037import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080038import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070039
Dan Albert8b72aef2015-03-23 19:13:21 -070040class Options(object):
41 def __init__(self):
42 platform_search_path = {
43 "linux2": "out/host/linux-x86",
44 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070045 }
Doug Zongker85448772014-09-09 14:59:20 -070046
Tao Bao76def242017-11-21 09:25:31 -080047 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080049 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070050 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080052 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070053 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070055 # use otatools built boot_signer by default
56 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070057 self.boot_signer_args = []
58 self.verity_signer_path = None
59 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.verbose = False
61 self.tempfiles = []
62 self.device_specific = None
63 self.extras = {}
64 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070065 self.source_info_dict = None
66 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070068 # Stash size cannot exceed cache_size * threshold.
69 self.cache_size = None
70 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070071
72
73OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070074
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080075
76# Values for "certificate" in apkcerts that mean special things.
77SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
78
Tao Bao9dd909e2017-11-14 11:27:32 -080079
80# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010081AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010082 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080083
84
Tianjie Xu209db462016-05-24 17:34:52 -070085class ErrorCode(object):
86 """Define error_codes for failures that happen during the actual
87 update package installation.
88
89 Error codes 0-999 are reserved for failures before the package
90 installation (i.e. low battery, package verification failure).
91 Detailed code in 'bootable/recovery/error_code.h' """
92
93 SYSTEM_VERIFICATION_FAILURE = 1000
94 SYSTEM_UPDATE_FAILURE = 1001
95 SYSTEM_UNEXPECTED_CONTENTS = 1002
96 SYSTEM_NONZERO_CONTENTS = 1003
97 SYSTEM_RECOVER_FAILURE = 1004
98 VENDOR_VERIFICATION_FAILURE = 2000
99 VENDOR_UPDATE_FAILURE = 2001
100 VENDOR_UNEXPECTED_CONTENTS = 2002
101 VENDOR_NONZERO_CONTENTS = 2003
102 VENDOR_RECOVER_FAILURE = 2004
103 OEM_PROP_MISMATCH = 3000
104 FINGERPRINT_MISMATCH = 3001
105 THUMBPRINT_MISMATCH = 3002
106 OLDER_BUILD = 3003
107 DEVICE_MISMATCH = 3004
108 BAD_PATCH_FILE = 3005
109 INSUFFICIENT_CACHE_SPACE = 3006
110 TUNE_PARTITION_FAILURE = 3007
111 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800112
Tao Bao80921982018-03-21 21:02:19 -0700113
Dan Albert8b72aef2015-03-23 19:13:21 -0700114class ExternalError(RuntimeError):
115 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700116
117
Tao Bao39451582017-05-04 11:10:47 -0700118def Run(args, verbose=None, **kwargs):
119 """Create and return a subprocess.Popen object.
120
121 Caller can specify if the command line should be printed. The global
122 OPTIONS.verbose will be used if not specified.
123 """
124 if verbose is None:
125 verbose = OPTIONS.verbose
126 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800127 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700128 return subprocess.Popen(args, **kwargs)
129
130
Tao Baoc765cca2018-01-31 17:32:40 -0800131def RoundUpTo4K(value):
132 rounded_up = value + 4095
133 return rounded_up - (rounded_up % 4096)
134
135
Ying Wang7e6d4e42010-12-13 16:25:36 -0800136def CloseInheritedPipes():
137 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
138 before doing other work."""
139 if platform.system() != "Darwin":
140 return
141 for d in range(3, 1025):
142 try:
143 stat = os.fstat(d)
144 if stat is not None:
145 pipebit = stat[0] & 0x1000
146 if pipebit != 0:
147 os.close(d)
148 except OSError:
149 pass
150
151
Tao Bao2c15d9e2015-07-09 11:51:16 -0700152def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700153 """Read and parse the META/misc_info.txt key/value pairs from the
154 input target files and return a dict."""
155
Doug Zongkerc9253822014-02-04 12:17:58 -0800156 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700157 if isinstance(input_file, zipfile.ZipFile):
158 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800159 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700160 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800161 try:
162 with open(path) as f:
163 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700164 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800165 if e.errno == errno.ENOENT:
166 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800167
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700168 try:
Michael Runge6e836112014-04-15 17:40:21 -0700169 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700170 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800171 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700172
Tao Bao6cd54732017-02-27 15:12:05 -0800173 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800174 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800175
Tao Bao84e75682015-07-19 02:38:53 -0700176 # A few properties are stored as links to the files in the out/ directory.
177 # It works fine with the build system. However, they are no longer available
178 # when (re)generating from target_files zip. If input_dir is not None, we
179 # are doing repacking. Redirect those properties to the actual files in the
180 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700181 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400182 # We carry a copy of file_contexts.bin under META/. If not available,
183 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tom Cherryd14b8952018-08-09 14:26:00 -0700184 # to build images than the one running on device, in that case, we must
185 # have the one for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700186 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
187 fc_config = os.path.join(input_dir, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700188 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700189
Tom Cherryd14b8952018-08-09 14:26:00 -0700190 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700191
Tom Cherryd14b8952018-08-09 14:26:00 -0700192 # Similarly we need to redirect "root_dir", and "root_fs_config".
193 d["root_dir"] = os.path.join(input_dir, "ROOT")
194 d["root_fs_config"] = os.path.join(
195 input_dir, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700196
Tao Baof54216f2016-03-29 15:12:37 -0700197 # Redirect {system,vendor}_base_fs_file.
198 if "system_base_fs_file" in d:
199 basename = os.path.basename(d["system_base_fs_file"])
200 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700201 if os.path.exists(system_base_fs_file):
202 d["system_base_fs_file"] = system_base_fs_file
203 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800204 print("Warning: failed to find system base fs file: %s" % (
205 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700206 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700207
208 if "vendor_base_fs_file" in d:
209 basename = os.path.basename(d["vendor_base_fs_file"])
210 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700211 if os.path.exists(vendor_base_fs_file):
212 d["vendor_base_fs_file"] = vendor_base_fs_file
213 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800214 print("Warning: failed to find vendor base fs file: %s" % (
215 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700216 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700217
Doug Zongker37974732010-09-16 17:44:38 -0700218 def makeint(key):
219 if key in d:
220 d[key] = int(d[key], 0)
221
222 makeint("recovery_api_version")
223 makeint("blocksize")
224 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700225 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700226 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700227 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700228 makeint("recovery_size")
229 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800230 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700231
Tao Baoa57ab9f2018-08-24 12:08:38 -0700232 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
233 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
234 # cases, since it may load the info_dict from an old build (e.g. when
235 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800236 system_root_image = d.get("system_root_image") == "true"
237 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700238 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700239 if isinstance(input_file, zipfile.ZipFile):
240 if recovery_fstab_path not in input_file.namelist():
241 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
242 else:
243 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
244 if not os.path.exists(path):
245 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800246 d["fstab"] = LoadRecoveryFSTab(
247 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700248
Tao Bao76def242017-11-21 09:25:31 -0800249 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700250 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700251 if isinstance(input_file, zipfile.ZipFile):
252 if recovery_fstab_path not in input_file.namelist():
253 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
254 else:
255 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
256 if not os.path.exists(path):
257 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800258 d["fstab"] = LoadRecoveryFSTab(
259 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700260
Tianjie Xucfa86222016-03-07 16:31:19 -0800261 else:
262 d["fstab"] = None
263
Tao Baobcd1d162017-08-26 13:10:26 -0700264 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
265 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Tao Bao12d87fc2018-01-31 12:18:52 -0800266
267 # Set up the salt (based on fingerprint or thumbprint) that will be used when
268 # adding AVB footer.
269 if d.get("avb_enable") == "true":
270 fp = None
271 if "build.prop" in d:
272 build_prop = d["build.prop"]
273 if "ro.build.fingerprint" in build_prop:
274 fp = build_prop["ro.build.fingerprint"]
275 elif "ro.build.thumbprint" in build_prop:
276 fp = build_prop["ro.build.thumbprint"]
277 if fp:
278 d["avb_salt"] = sha256(fp).hexdigest()
279
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700280 return d
281
Tao Baod1de6f32017-03-01 16:38:48 -0800282
Tao Baobcd1d162017-08-26 13:10:26 -0700283def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700284 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700285 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700286 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700287 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700288 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700289 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700290
Tao Baod1de6f32017-03-01 16:38:48 -0800291
Michael Runge6e836112014-04-15 17:40:21 -0700292def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700293 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700294 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700295 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700296 if not line or line.startswith("#"):
297 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700298 if "=" in line:
299 name, value = line.split("=", 1)
300 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700301 return d
302
Tao Baod1de6f32017-03-01 16:38:48 -0800303
Tianjie Xucfa86222016-03-07 16:31:19 -0800304def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
305 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700306 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800307 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700308 self.mount_point = mount_point
309 self.fs_type = fs_type
310 self.device = device
311 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700312 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700313
314 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800315 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700316 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800317 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700318 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700319
Tao Baod1de6f32017-03-01 16:38:48 -0800320 assert fstab_version == 2
321
322 d = {}
323 for line in data.split("\n"):
324 line = line.strip()
325 if not line or line.startswith("#"):
326 continue
327
328 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
329 pieces = line.split()
330 if len(pieces) != 5:
331 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
332
333 # Ignore entries that are managed by vold.
334 options = pieces[4]
335 if "voldmanaged=" in options:
336 continue
337
338 # It's a good line, parse it.
339 length = 0
340 options = options.split(",")
341 for i in options:
342 if i.startswith("length="):
343 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800344 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800345 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700346 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800347
Tao Baod1de6f32017-03-01 16:38:48 -0800348 mount_flags = pieces[3]
349 # Honor the SELinux context if present.
350 context = None
351 for i in mount_flags.split(","):
352 if i.startswith("context="):
353 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800354
Tao Baod1de6f32017-03-01 16:38:48 -0800355 mount_point = pieces[1]
356 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
357 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800358
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700359 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700360 # system. Other areas assume system is always at "/system" so point /system
361 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700362 if system_root_image:
363 assert not d.has_key("/system") and d.has_key("/")
364 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700365 return d
366
367
Doug Zongker37974732010-09-16 17:44:38 -0700368def DumpInfoDict(d):
369 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800370 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700371
Dan Albert8b72aef2015-03-23 19:13:21 -0700372
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800373def AppendAVBSigningArgs(cmd, partition):
374 """Append signing arguments for avbtool."""
375 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
376 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
377 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
378 if key_path and algorithm:
379 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700380 avb_salt = OPTIONS.info_dict.get("avb_salt")
381 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
382 if avb_salt and partition != "vbmeta":
383 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800384
385
Tao Bao02a08592018-07-22 12:40:45 -0700386def GetAvbChainedPartitionArg(partition, info_dict, key=None):
387 """Constructs and returns the arg to build or verify a chained partition.
388
389 Args:
390 partition: The partition name.
391 info_dict: The info dict to look up the key info and rollback index
392 location.
393 key: The key to be used for building or verifying the partition. Defaults to
394 the key listed in info_dict.
395
396 Returns:
397 A string of form "partition:rollback_index_location:key" that can be used to
398 build or verify vbmeta image.
399
400 Raises:
401 AssertionError: When it fails to extract the public key with avbtool.
402 """
403 if key is None:
404 key = info_dict["avb_" + partition + "_key_path"]
405 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
406 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
407 proc = Run(
408 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path],
409 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
410 stdoutdata, _ = proc.communicate()
411 assert proc.returncode == 0, \
412 "Failed to extract pubkey for {}:\n{}".format(
413 partition, stdoutdata)
414
415 rollback_index_location = info_dict[
416 "avb_" + partition + "_rollback_index_location"]
417 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
418
419
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700420def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800421 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700422 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700423
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700424 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800425 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
426 we are building a two-step special image (i.e. building a recovery image to
427 be loaded into /boot in two-step OTAs).
428
429 Return the image data, or None if sourcedir does not appear to contains files
430 for building the requested image.
431 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700432
433 def make_ramdisk():
434 ramdisk_img = tempfile.NamedTemporaryFile()
435
436 if os.access(fs_config_file, os.F_OK):
437 cmd = ["mkbootfs", "-f", fs_config_file,
438 os.path.join(sourcedir, "RAMDISK")]
439 else:
440 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
441 p1 = Run(cmd, stdout=subprocess.PIPE)
442 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
443
444 p2.wait()
445 p1.wait()
446 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
447 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
448
449 return ramdisk_img
450
451 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
452 return None
453
454 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700455 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700456
Doug Zongkerd5131602012-08-02 14:46:42 -0700457 if info_dict is None:
458 info_dict = OPTIONS.info_dict
459
Doug Zongkereef39442009-04-02 12:14:19 -0700460 img = tempfile.NamedTemporaryFile()
461
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700462 if has_ramdisk:
463 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700464
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800465 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
466 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
467
468 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700469
Benoit Fradina45a8682014-07-14 21:00:43 +0200470 fn = os.path.join(sourcedir, "second")
471 if os.access(fn, os.F_OK):
472 cmd.append("--second")
473 cmd.append(fn)
474
Doug Zongker171f1cd2009-06-15 22:36:37 -0700475 fn = os.path.join(sourcedir, "cmdline")
476 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700477 cmd.append("--cmdline")
478 cmd.append(open(fn).read().rstrip("\n"))
479
480 fn = os.path.join(sourcedir, "base")
481 if os.access(fn, os.F_OK):
482 cmd.append("--base")
483 cmd.append(open(fn).read().rstrip("\n"))
484
Ying Wang4de6b5b2010-08-25 14:29:34 -0700485 fn = os.path.join(sourcedir, "pagesize")
486 if os.access(fn, os.F_OK):
487 cmd.append("--pagesize")
488 cmd.append(open(fn).read().rstrip("\n"))
489
Tao Bao76def242017-11-21 09:25:31 -0800490 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700491 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700492 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700493
Tao Bao76def242017-11-21 09:25:31 -0800494 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000495 if args and args.strip():
496 cmd.extend(shlex.split(args))
497
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700498 if has_ramdisk:
499 cmd.extend(["--ramdisk", ramdisk_img.name])
500
Tao Baod95e9fd2015-03-29 23:07:41 -0700501 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800502 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700503 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700504 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700505 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700506 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700507
Tao Baobf70c3182017-07-11 17:27:55 -0700508 # "boot" or "recovery", without extension.
509 partition_name = os.path.basename(sourcedir).lower()
510
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700511 if (partition_name == "recovery" and
512 info_dict.get("include_recovery_dtbo") == "true"):
513 fn = os.path.join(sourcedir, "recovery_dtbo")
514 cmd.extend(["--recovery_dtbo", fn])
515
Doug Zongker38a649f2009-06-17 09:07:09 -0700516 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700517 p.communicate()
Tao Baobf70c3182017-07-11 17:27:55 -0700518 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700519
Tao Bao76def242017-11-21 09:25:31 -0800520 if (info_dict.get("boot_signer") == "true" and
521 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800522 # Hard-code the path as "/boot" for two-step special recovery image (which
523 # will be loaded into /boot during the two-step OTA).
524 if two_step_image:
525 path = "/boot"
526 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700527 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700528 cmd = [OPTIONS.boot_signer_path]
529 cmd.extend(OPTIONS.boot_signer_args)
530 cmd.extend([path, img.name,
531 info_dict["verity_key"] + ".pk8",
532 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700533 p = Run(cmd, stdout=subprocess.PIPE)
534 p.communicate()
535 assert p.returncode == 0, "boot_signer of %s image failed" % path
536
Tao Baod95e9fd2015-03-29 23:07:41 -0700537 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800538 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700539 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700540 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800541 # We have switched from the prebuilt futility binary to using the tool
542 # (futility-host) built from the source. Override the setting in the old
543 # TF.zip.
544 futility = info_dict["futility"]
545 if futility.startswith("prebuilts/"):
546 futility = "futility-host"
547 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700548 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700549 info_dict["vboot_key"] + ".vbprivk",
550 info_dict["vboot_subkey"] + ".vbprivk",
551 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700552 img.name]
553 p = Run(cmd, stdout=subprocess.PIPE)
554 p.communicate()
555 assert p.returncode == 0, "vboot_signer of %s image failed" % path
556
Tao Baof3282b42015-04-01 11:21:55 -0700557 # Clean up the temp files.
558 img_unsigned.close()
559 img_keyblock.close()
560
David Zeuthen8fecb282017-12-01 16:24:01 -0500561 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800562 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700563 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500564 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400565 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700566 "--partition_size", str(part_size), "--partition_name",
567 partition_name]
568 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500569 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400570 if args and args.strip():
571 cmd.extend(shlex.split(args))
572 p = Run(cmd, stdout=subprocess.PIPE)
573 p.communicate()
574 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c3182017-07-11 17:27:55 -0700575 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500576
577 img.seek(os.SEEK_SET, 0)
578 data = img.read()
579
580 if has_ramdisk:
581 ramdisk_img.close()
582 img.close()
583
584 return data
585
586
Doug Zongkerd5131602012-08-02 14:46:42 -0700587def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800588 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700589 """Return a File object with the desired bootable image.
590
591 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
592 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
593 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700594
Doug Zongker55d93282011-01-25 17:03:34 -0800595 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
596 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800597 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800598 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700599
600 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
601 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800602 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700603 return File.FromLocalFile(name, prebuilt_path)
604
Tao Bao89fbb0f2017-01-10 10:47:58 -0800605 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700606
607 if info_dict is None:
608 info_dict = OPTIONS.info_dict
609
610 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800611 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
612 # for recovery.
613 has_ramdisk = (info_dict.get("system_root_image") != "true" or
614 prebuilt_name != "boot.img" or
615 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700616
Doug Zongker6f1d0312014-08-22 08:07:12 -0700617 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400618 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
619 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800620 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700621 if data:
622 return File(name, data)
623 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800624
Doug Zongkereef39442009-04-02 12:14:19 -0700625
Narayan Kamatha07bf042017-08-14 14:49:21 +0100626def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800627 """Gunzips the given gzip compressed file to a given output file."""
628 with gzip.open(in_filename, "rb") as in_file, \
629 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100630 shutil.copyfileobj(in_file, out_file)
631
632
Doug Zongker75f17362009-12-08 13:46:44 -0800633def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800634 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800635
Tao Bao1c830bf2017-12-25 10:43:47 -0800636 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
637 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800638
Tao Bao1c830bf2017-12-25 10:43:47 -0800639 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800640 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800641 """
Doug Zongkereef39442009-04-02 12:14:19 -0700642
Doug Zongker55d93282011-01-25 17:03:34 -0800643 def unzip_to_dir(filename, dirname):
644 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
645 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800646 cmd.extend(pattern)
Tao Bao80921982018-03-21 21:02:19 -0700647 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
648 stdoutdata, _ = p.communicate()
Doug Zongker55d93282011-01-25 17:03:34 -0800649 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700650 raise ExternalError(
651 "Failed to unzip input target-files \"{}\":\n{}".format(
652 filename, stdoutdata))
Doug Zongker55d93282011-01-25 17:03:34 -0800653
Tao Bao1c830bf2017-12-25 10:43:47 -0800654 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800655 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
656 if m:
657 unzip_to_dir(m.group(1), tmp)
658 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
659 filename = m.group(1)
660 else:
661 unzip_to_dir(filename, tmp)
662
Tao Baodba59ee2018-01-09 13:21:02 -0800663 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700664
665
Tao Baoe709b092018-02-07 12:40:00 -0800666def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -0800667 """Returns a SparseImage object suitable for passing to BlockImageDiff.
668
669 This function loads the specified sparse image from the given path, and
670 performs additional processing for OTA purpose. For example, it always adds
671 block 0 to clobbered blocks list. It also detects files that cannot be
672 reconstructed from the block list, for whom we should avoid applying imgdiff.
673
674 Args:
675 which: The partition name, which must be "system" or "vendor".
676 tmpdir: The directory that contains the prebuilt image and block map file.
677 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800678 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -0800679
680 Returns:
681 A SparseImage object, with file_map info loaded.
682 """
683 assert which in ("system", "vendor")
684
685 path = os.path.join(tmpdir, "IMAGES", which + ".img")
686 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
687
688 # The image and map files must have been created prior to calling
689 # ota_from_target_files.py (since LMP).
690 assert os.path.exists(path) and os.path.exists(mappath)
691
692 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
693 # it to clobbered_blocks so that it will be written to the target
694 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
695 clobbered_blocks = "0"
696
Tao Baoe709b092018-02-07 12:40:00 -0800697 image = sparse_img.SparseImage(path, mappath, clobbered_blocks,
698 allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -0800699
700 # block.map may contain less blocks, because mke2fs may skip allocating blocks
701 # if they contain all zeros. We can't reconstruct such a file from its block
702 # list. Tag such entries accordingly. (Bug: 65213616)
703 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800704 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700705 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800706 continue
707
Tom Cherryd14b8952018-08-09 14:26:00 -0700708 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
709 # filename listed in system.map may contain an additional leading slash
710 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
711 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700712 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
713
Tom Cherryd14b8952018-08-09 14:26:00 -0700714 # Special handling another case, where files not under /system
715 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700716 if which == 'system' and not arcname.startswith('SYSTEM'):
717 arcname = 'ROOT/' + arcname
718
719 assert arcname in input_zip.namelist(), \
720 "Failed to find the ZIP entry for {}".format(entry)
721
Tao Baoc765cca2018-01-31 17:32:40 -0800722 info = input_zip.getinfo(arcname)
723 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800724
725 # If a RangeSet has been tagged as using shared blocks while loading the
726 # image, its block list must be already incomplete due to that reason. Don't
727 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
728 if ranges.extra.get('uses_shared_blocks'):
729 continue
730
Tao Baoc765cca2018-01-31 17:32:40 -0800731 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
732 ranges.extra['incomplete'] = True
733
734 return image
735
736
Doug Zongkereef39442009-04-02 12:14:19 -0700737def GetKeyPasswords(keylist):
738 """Given a list of keys, prompt the user to enter passwords for
739 those which require them. Return a {key: password} dict. password
740 will be None if the key has no password."""
741
Doug Zongker8ce7c252009-05-22 13:34:54 -0700742 no_passwords = []
743 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700744 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700745 devnull = open("/dev/null", "w+b")
746 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800747 # We don't need a password for things that aren't really keys.
748 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700749 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700750 continue
751
T.R. Fullhart37e10522013-03-18 10:31:26 -0700752 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700753 "-inform", "DER", "-nocrypt"],
754 stdin=devnull.fileno(),
755 stdout=devnull.fileno(),
756 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700757 p.communicate()
758 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700759 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700760 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700761 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700762 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
763 "-inform", "DER", "-passin", "pass:"],
764 stdin=devnull.fileno(),
765 stdout=devnull.fileno(),
766 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700767 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700768 if p.returncode == 0:
769 # Encrypted key with empty string as password.
770 key_passwords[k] = ''
771 elif stderr.startswith('Error decrypting key'):
772 # Definitely encrypted key.
773 # It would have said "Error reading key" if it didn't parse correctly.
774 need_passwords.append(k)
775 else:
776 # Potentially, a type of key that openssl doesn't understand.
777 # We'll let the routines in signapk.jar handle it.
778 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700779 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700780
T.R. Fullhart37e10522013-03-18 10:31:26 -0700781 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800782 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700783 return key_passwords
784
785
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800786def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700787 """Gets the minSdkVersion declared in the APK.
788
789 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
790 This can be both a decimal number (API Level) or a codename.
791
792 Args:
793 apk_name: The APK filename.
794
795 Returns:
796 The parsed SDK version string.
797
798 Raises:
799 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800800 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700801 proc = Run(
802 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
803 stderr=subprocess.PIPE)
804 stdoutdata, stderrdata = proc.communicate()
805 if proc.returncode != 0:
806 raise ExternalError(
807 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
808 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800809
Tao Baof47bf0f2018-03-21 23:28:51 -0700810 for line in stdoutdata.split("\n"):
811 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800812 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
813 if m:
814 return m.group(1)
815 raise ExternalError("No minSdkVersion returned by aapt")
816
817
818def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700819 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800820
Tao Baof47bf0f2018-03-21 23:28:51 -0700821 If minSdkVersion is set to a codename, it is translated to a number using the
822 provided map.
823
824 Args:
825 apk_name: The APK filename.
826
827 Returns:
828 The parsed SDK version number.
829
830 Raises:
831 ExternalError: On failing to get the min SDK version number.
832 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800833 version = GetMinSdkVersion(apk_name)
834 try:
835 return int(version)
836 except ValueError:
837 # Not a decimal number. Codename?
838 if version in codename_to_api_level_map:
839 return codename_to_api_level_map[version]
840 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700841 raise ExternalError(
842 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
843 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800844
845
846def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800847 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700848 """Sign the input_name zip/jar/apk, producing output_name. Use the
849 given key and password (the latter may be None if the key does not
850 have a password.
851
Doug Zongker951495f2009-08-14 12:44:19 -0700852 If whole_file is true, use the "-w" option to SignApk to embed a
853 signature that covers the whole file in the archive comment of the
854 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800855
856 min_api_level is the API Level (int) of the oldest platform this file may end
857 up on. If not specified for an APK, the API Level is obtained by interpreting
858 the minSdkVersion attribute of the APK's AndroidManifest.xml.
859
860 codename_to_api_level_map is needed to translate the codename which may be
861 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700862 """
Tao Bao76def242017-11-21 09:25:31 -0800863 if codename_to_api_level_map is None:
864 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700865
Alex Klyubin9667b182015-12-10 13:38:50 -0800866 java_library_path = os.path.join(
867 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
868
Tao Baoe95540e2016-11-08 12:08:53 -0800869 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
870 ["-Djava.library.path=" + java_library_path,
871 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
872 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700873 if whole_file:
874 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800875
876 min_sdk_version = min_api_level
877 if min_sdk_version is None:
878 if not whole_file:
879 min_sdk_version = GetMinSdkVersionInt(
880 input_name, codename_to_api_level_map)
881 if min_sdk_version is not None:
882 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
883
T.R. Fullhart37e10522013-03-18 10:31:26 -0700884 cmd.extend([key + OPTIONS.public_key_suffix,
885 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800886 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700887
Tao Bao80921982018-03-21 21:02:19 -0700888 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
889 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700890 if password is not None:
891 password += "\n"
Tao Bao80921982018-03-21 21:02:19 -0700892 stdoutdata, _ = p.communicate(password)
Doug Zongkereef39442009-04-02 12:14:19 -0700893 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700894 raise ExternalError(
895 "Failed to run signapk.jar: return code {}:\n{}".format(
896 p.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700897
Doug Zongkereef39442009-04-02 12:14:19 -0700898
Doug Zongker37974732010-09-16 17:44:38 -0700899def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800900 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700901
Tao Bao9dd909e2017-11-14 11:27:32 -0800902 For non-AVB images, raise exception if the data is too big. Print a warning
903 if the data is nearing the maximum size.
904
905 For AVB images, the actual image size should be identical to the limit.
906
907 Args:
908 data: A string that contains all the data for the partition.
909 target: The partition name. The ".img" suffix is optional.
910 info_dict: The dict to be looked up for relevant info.
911 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700912 if target.endswith(".img"):
913 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700914 mount_point = "/" + target
915
Ying Wangf8824af2014-06-03 14:07:27 -0700916 fs_type = None
917 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700918 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700919 if mount_point == "/userdata":
920 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700921 p = info_dict["fstab"][mount_point]
922 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800923 device = p.device
924 if "/" in device:
925 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -0800926 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -0700927 if not fs_type or not limit:
928 return
Doug Zongkereef39442009-04-02 12:14:19 -0700929
Andrew Boie0f9aec82012-02-14 09:32:52 -0800930 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800931 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
932 # path.
933 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
934 if size != limit:
935 raise ExternalError(
936 "Mismatching image size for %s: expected %d actual %d" % (
937 target, limit, size))
938 else:
939 pct = float(size) * 100.0 / limit
940 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
941 if pct >= 99.0:
942 raise ExternalError(msg)
943 elif pct >= 95.0:
944 print("\n WARNING: %s\n" % (msg,))
945 elif OPTIONS.verbose:
946 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700947
948
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800949def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800950 """Parses the APK certs info from a given target-files zip.
951
952 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
953 tuple with the following elements: (1) a dictionary that maps packages to
954 certs (based on the "certificate" and "private_key" attributes in the file;
955 (2) a string representing the extension of compressed APKs in the target files
956 (e.g ".gz", ".bro").
957
958 Args:
959 tf_zip: The input target_files ZipFile (already open).
960
961 Returns:
962 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
963 the extension string of compressed APKs (e.g. ".gz"), or None if there's
964 no compressed APKs.
965 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800966 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100967 compressed_extension = None
968
Tao Bao0f990332017-09-08 19:02:54 -0700969 # META/apkcerts.txt contains the info for _all_ the packages known at build
970 # time. Filter out the ones that are not installed.
971 installed_files = set()
972 for name in tf_zip.namelist():
973 basename = os.path.basename(name)
974 if basename:
975 installed_files.add(basename)
976
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800977 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
978 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700979 if not line:
980 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800981 m = re.match(
982 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
983 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
984 line)
985 if not m:
986 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100987
Tao Bao818ddf52018-01-05 11:17:34 -0800988 matches = m.groupdict()
989 cert = matches["CERT"]
990 privkey = matches["PRIVKEY"]
991 name = matches["NAME"]
992 this_compressed_extension = matches["COMPRESSED"]
993
994 public_key_suffix_len = len(OPTIONS.public_key_suffix)
995 private_key_suffix_len = len(OPTIONS.private_key_suffix)
996 if cert in SPECIAL_CERT_STRINGS and not privkey:
997 certmap[name] = cert
998 elif (cert.endswith(OPTIONS.public_key_suffix) and
999 privkey.endswith(OPTIONS.private_key_suffix) and
1000 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1001 certmap[name] = cert[:-public_key_suffix_len]
1002 else:
1003 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1004
1005 if not this_compressed_extension:
1006 continue
1007
1008 # Only count the installed files.
1009 filename = name + '.' + this_compressed_extension
1010 if filename not in installed_files:
1011 continue
1012
1013 # Make sure that all the values in the compression map have the same
1014 # extension. We don't support multiple compression methods in the same
1015 # system image.
1016 if compressed_extension:
1017 if this_compressed_extension != compressed_extension:
1018 raise ValueError(
1019 "Multiple compressed extensions: {} vs {}".format(
1020 compressed_extension, this_compressed_extension))
1021 else:
1022 compressed_extension = this_compressed_extension
1023
1024 return (certmap,
1025 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001026
1027
Doug Zongkereef39442009-04-02 12:14:19 -07001028COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001029Global options
1030
1031 -p (--path) <dir>
1032 Prepend <dir>/bin to the list of places to search for binaries run by this
1033 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001034
Doug Zongker05d3dea2009-06-22 11:32:31 -07001035 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001036 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001037
Tao Bao30df8b42018-04-23 15:32:53 -07001038 -x (--extra) <key=value>
1039 Add a key/value pair to the 'extras' dict, which device-specific extension
1040 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001041
Doug Zongkereef39442009-04-02 12:14:19 -07001042 -v (--verbose)
1043 Show command lines being executed.
1044
1045 -h (--help)
1046 Display this usage message and exit.
1047"""
1048
1049def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001050 print(docstring.rstrip("\n"))
1051 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001052
1053
1054def ParseOptions(argv,
1055 docstring,
1056 extra_opts="", extra_long_opts=(),
1057 extra_option_handler=None):
1058 """Parse the options in argv and return any arguments that aren't
1059 flags. docstring is the calling module's docstring, to be displayed
1060 for errors and -h. extra_opts and extra_long_opts are for flags
1061 defined by the caller, which are processed by passing them to
1062 extra_option_handler."""
1063
1064 try:
1065 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001066 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001067 ["help", "verbose", "path=", "signapk_path=",
1068 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001069 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001070 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1071 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001072 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001073 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001074 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001075 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001076 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001077 sys.exit(2)
1078
Doug Zongkereef39442009-04-02 12:14:19 -07001079 for o, a in opts:
1080 if o in ("-h", "--help"):
1081 Usage(docstring)
1082 sys.exit()
1083 elif o in ("-v", "--verbose"):
1084 OPTIONS.verbose = True
1085 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001086 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001087 elif o in ("--signapk_path",):
1088 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001089 elif o in ("--signapk_shared_library_path",):
1090 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001091 elif o in ("--extra_signapk_args",):
1092 OPTIONS.extra_signapk_args = shlex.split(a)
1093 elif o in ("--java_path",):
1094 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001095 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001096 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001097 elif o in ("--public_key_suffix",):
1098 OPTIONS.public_key_suffix = a
1099 elif o in ("--private_key_suffix",):
1100 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001101 elif o in ("--boot_signer_path",):
1102 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001103 elif o in ("--boot_signer_args",):
1104 OPTIONS.boot_signer_args = shlex.split(a)
1105 elif o in ("--verity_signer_path",):
1106 OPTIONS.verity_signer_path = a
1107 elif o in ("--verity_signer_args",):
1108 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001109 elif o in ("-s", "--device_specific"):
1110 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001111 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001112 key, value = a.split("=", 1)
1113 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001114 else:
1115 if extra_option_handler is None or not extra_option_handler(o, a):
1116 assert False, "unknown option \"%s\"" % (o,)
1117
Doug Zongker85448772014-09-09 14:59:20 -07001118 if OPTIONS.search_path:
1119 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1120 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001121
1122 return args
1123
1124
Tao Bao4c851b12016-09-19 13:54:38 -07001125def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001126 """Make a temp file and add it to the list of things to be deleted
1127 when Cleanup() is called. Return the filename."""
1128 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1129 os.close(fd)
1130 OPTIONS.tempfiles.append(fn)
1131 return fn
1132
1133
Tao Bao1c830bf2017-12-25 10:43:47 -08001134def MakeTempDir(prefix='tmp', suffix=''):
1135 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1136
1137 Returns:
1138 The absolute pathname of the new directory.
1139 """
1140 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1141 OPTIONS.tempfiles.append(dir_name)
1142 return dir_name
1143
1144
Doug Zongkereef39442009-04-02 12:14:19 -07001145def Cleanup():
1146 for i in OPTIONS.tempfiles:
1147 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001148 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001149 else:
1150 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001151 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001152
1153
1154class PasswordManager(object):
1155 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001156 self.editor = os.getenv("EDITOR")
1157 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001158
1159 def GetPasswords(self, items):
1160 """Get passwords corresponding to each string in 'items',
1161 returning a dict. (The dict may have keys in addition to the
1162 values in 'items'.)
1163
1164 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1165 user edit that file to add more needed passwords. If no editor is
1166 available, or $ANDROID_PW_FILE isn't define, prompts the user
1167 interactively in the ordinary way.
1168 """
1169
1170 current = self.ReadFile()
1171
1172 first = True
1173 while True:
1174 missing = []
1175 for i in items:
1176 if i not in current or not current[i]:
1177 missing.append(i)
1178 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001179 if not missing:
1180 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001181
1182 for i in missing:
1183 current[i] = ""
1184
1185 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001186 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001187 answer = raw_input("try to edit again? [y]> ").strip()
1188 if answer and answer[0] not in 'yY':
1189 raise RuntimeError("key passwords unavailable")
1190 first = False
1191
1192 current = self.UpdateAndReadFile(current)
1193
Dan Albert8b72aef2015-03-23 19:13:21 -07001194 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001195 """Prompt the user to enter a value (password) for each key in
1196 'current' whose value is fales. Returns a new dict with all the
1197 values.
1198 """
1199 result = {}
1200 for k, v in sorted(current.iteritems()):
1201 if v:
1202 result[k] = v
1203 else:
1204 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001205 result[k] = getpass.getpass(
1206 "Enter password for %s key> " % k).strip()
1207 if result[k]:
1208 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001209 return result
1210
1211 def UpdateAndReadFile(self, current):
1212 if not self.editor or not self.pwfile:
1213 return self.PromptResult(current)
1214
1215 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001216 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001217 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1218 f.write("# (Additional spaces are harmless.)\n\n")
1219
1220 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001221 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1222 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001223 f.write("[[[ %s ]]] %s\n" % (v, k))
1224 if not v and first_line is None:
1225 # position cursor on first line with no password.
1226 first_line = i + 4
1227 f.close()
1228
1229 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1230 _, _ = p.communicate()
1231
1232 return self.ReadFile()
1233
1234 def ReadFile(self):
1235 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001236 if self.pwfile is None:
1237 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001238 try:
1239 f = open(self.pwfile, "r")
1240 for line in f:
1241 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001242 if not line or line[0] == '#':
1243 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001244 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1245 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001246 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001247 else:
1248 result[m.group(2)] = m.group(1)
1249 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001250 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001251 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001252 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001253 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001254
1255
Dan Albert8e0178d2015-01-27 15:53:15 -08001256def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1257 compress_type=None):
1258 import datetime
1259
1260 # http://b/18015246
1261 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1262 # for files larger than 2GiB. We can work around this by adjusting their
1263 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1264 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1265 # it isn't clear to me exactly what circumstances cause this).
1266 # `zipfile.write()` must be used directly to work around this.
1267 #
1268 # This mess can be avoided if we port to python3.
1269 saved_zip64_limit = zipfile.ZIP64_LIMIT
1270 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1271
1272 if compress_type is None:
1273 compress_type = zip_file.compression
1274 if arcname is None:
1275 arcname = filename
1276
1277 saved_stat = os.stat(filename)
1278
1279 try:
1280 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1281 # file to be zipped and reset it when we're done.
1282 os.chmod(filename, perms)
1283
1284 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001285 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1286 # intentional. zip stores datetimes in local time without a time zone
1287 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1288 # in the zip archive.
1289 local_epoch = datetime.datetime.fromtimestamp(0)
1290 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001291 os.utime(filename, (timestamp, timestamp))
1292
1293 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1294 finally:
1295 os.chmod(filename, saved_stat.st_mode)
1296 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1297 zipfile.ZIP64_LIMIT = saved_zip64_limit
1298
1299
Tao Bao58c1b962015-05-20 09:32:18 -07001300def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001301 compress_type=None):
1302 """Wrap zipfile.writestr() function to work around the zip64 limit.
1303
1304 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1305 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1306 when calling crc32(bytes).
1307
1308 But it still works fine to write a shorter string into a large zip file.
1309 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1310 when we know the string won't be too long.
1311 """
1312
1313 saved_zip64_limit = zipfile.ZIP64_LIMIT
1314 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1315
1316 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1317 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001318 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001319 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001320 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001321 else:
Tao Baof3282b42015-04-01 11:21:55 -07001322 zinfo = zinfo_or_arcname
1323
1324 # If compress_type is given, it overrides the value in zinfo.
1325 if compress_type is not None:
1326 zinfo.compress_type = compress_type
1327
Tao Bao58c1b962015-05-20 09:32:18 -07001328 # If perms is given, it has a priority.
1329 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001330 # If perms doesn't set the file type, mark it as a regular file.
1331 if perms & 0o770000 == 0:
1332 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001333 zinfo.external_attr = perms << 16
1334
Tao Baof3282b42015-04-01 11:21:55 -07001335 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001336 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1337
Dan Albert8b72aef2015-03-23 19:13:21 -07001338 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001339 zipfile.ZIP64_LIMIT = saved_zip64_limit
1340
1341
Tao Bao89d7ab22017-12-14 17:05:33 -08001342def ZipDelete(zip_filename, entries):
1343 """Deletes entries from a ZIP file.
1344
1345 Since deleting entries from a ZIP file is not supported, it shells out to
1346 'zip -d'.
1347
1348 Args:
1349 zip_filename: The name of the ZIP file.
1350 entries: The name of the entry, or the list of names to be deleted.
1351
1352 Raises:
1353 AssertionError: In case of non-zero return from 'zip'.
1354 """
1355 if isinstance(entries, basestring):
1356 entries = [entries]
1357 cmd = ["zip", "-d", zip_filename] + entries
1358 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1359 stdoutdata, _ = proc.communicate()
1360 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1361 stdoutdata)
1362
1363
Tao Baof3282b42015-04-01 11:21:55 -07001364def ZipClose(zip_file):
1365 # http://b/18015246
1366 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1367 # central directory.
1368 saved_zip64_limit = zipfile.ZIP64_LIMIT
1369 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1370
1371 zip_file.close()
1372
1373 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001374
1375
1376class DeviceSpecificParams(object):
1377 module = None
1378 def __init__(self, **kwargs):
1379 """Keyword arguments to the constructor become attributes of this
1380 object, which is passed to all functions in the device-specific
1381 module."""
1382 for k, v in kwargs.iteritems():
1383 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001384 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001385
1386 if self.module is None:
1387 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001388 if not path:
1389 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001390 try:
1391 if os.path.isdir(path):
1392 info = imp.find_module("releasetools", [path])
1393 else:
1394 d, f = os.path.split(path)
1395 b, x = os.path.splitext(f)
1396 if x == ".py":
1397 f = b
1398 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001399 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001400 self.module = imp.load_module("device_specific", *info)
1401 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001402 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001403
1404 def _DoCall(self, function_name, *args, **kwargs):
1405 """Call the named function in the device-specific module, passing
1406 the given args and kwargs. The first argument to the call will be
1407 the DeviceSpecific object itself. If there is no module, or the
1408 module does not define the function, return the value of the
1409 'default' kwarg (which itself defaults to None)."""
1410 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001411 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001412 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1413
1414 def FullOTA_Assertions(self):
1415 """Called after emitting the block of assertions at the top of a
1416 full OTA package. Implementations can add whatever additional
1417 assertions they like."""
1418 return self._DoCall("FullOTA_Assertions")
1419
Doug Zongkere5ff5902012-01-17 10:55:37 -08001420 def FullOTA_InstallBegin(self):
1421 """Called at the start of full OTA installation."""
1422 return self._DoCall("FullOTA_InstallBegin")
1423
Doug Zongker05d3dea2009-06-22 11:32:31 -07001424 def FullOTA_InstallEnd(self):
1425 """Called at the end of full OTA installation; typically this is
1426 used to install the image for the device's baseband processor."""
1427 return self._DoCall("FullOTA_InstallEnd")
1428
1429 def IncrementalOTA_Assertions(self):
1430 """Called after emitting the block of assertions at the top of an
1431 incremental OTA package. Implementations can add whatever
1432 additional assertions they like."""
1433 return self._DoCall("IncrementalOTA_Assertions")
1434
Doug Zongkere5ff5902012-01-17 10:55:37 -08001435 def IncrementalOTA_VerifyBegin(self):
1436 """Called at the start of the verification phase of incremental
1437 OTA installation; additional checks can be placed here to abort
1438 the script before any changes are made."""
1439 return self._DoCall("IncrementalOTA_VerifyBegin")
1440
Doug Zongker05d3dea2009-06-22 11:32:31 -07001441 def IncrementalOTA_VerifyEnd(self):
1442 """Called at the end of the verification phase of incremental OTA
1443 installation; additional checks can be placed here to abort the
1444 script before any changes are made."""
1445 return self._DoCall("IncrementalOTA_VerifyEnd")
1446
Doug Zongkere5ff5902012-01-17 10:55:37 -08001447 def IncrementalOTA_InstallBegin(self):
1448 """Called at the start of incremental OTA installation (after
1449 verification is complete)."""
1450 return self._DoCall("IncrementalOTA_InstallBegin")
1451
Doug Zongker05d3dea2009-06-22 11:32:31 -07001452 def IncrementalOTA_InstallEnd(self):
1453 """Called at the end of incremental OTA installation; typically
1454 this is used to install the image for the device's baseband
1455 processor."""
1456 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001457
Tao Bao9bc6bb22015-11-09 16:58:28 -08001458 def VerifyOTA_Assertions(self):
1459 return self._DoCall("VerifyOTA_Assertions")
1460
Tao Bao76def242017-11-21 09:25:31 -08001461
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001462class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001463 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001464 self.name = name
1465 self.data = data
1466 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001467 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001468 self.sha1 = sha1(data).hexdigest()
1469
1470 @classmethod
1471 def FromLocalFile(cls, name, diskname):
1472 f = open(diskname, "rb")
1473 data = f.read()
1474 f.close()
1475 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001476
1477 def WriteToTemp(self):
1478 t = tempfile.NamedTemporaryFile()
1479 t.write(self.data)
1480 t.flush()
1481 return t
1482
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001483 def WriteToDir(self, d):
1484 with open(os.path.join(d, self.name), "wb") as fp:
1485 fp.write(self.data)
1486
Geremy Condra36bd3652014-02-06 19:45:10 -08001487 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001488 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001489
Tao Bao76def242017-11-21 09:25:31 -08001490
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001491DIFF_PROGRAM_BY_EXT = {
1492 ".gz" : "imgdiff",
1493 ".zip" : ["imgdiff", "-z"],
1494 ".jar" : ["imgdiff", "-z"],
1495 ".apk" : ["imgdiff", "-z"],
1496 ".img" : "imgdiff",
1497 }
1498
Tao Bao76def242017-11-21 09:25:31 -08001499
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001500class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001501 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001502 self.tf = tf
1503 self.sf = sf
1504 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001505 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001506
1507 def ComputePatch(self):
1508 """Compute the patch (as a string of data) needed to turn sf into
1509 tf. Returns the same tuple as GetPatch()."""
1510
1511 tf = self.tf
1512 sf = self.sf
1513
Doug Zongker24cd2802012-08-14 16:36:15 -07001514 if self.diff_program:
1515 diff_program = self.diff_program
1516 else:
1517 ext = os.path.splitext(tf.name)[1]
1518 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001519
1520 ttemp = tf.WriteToTemp()
1521 stemp = sf.WriteToTemp()
1522
1523 ext = os.path.splitext(tf.name)[1]
1524
1525 try:
1526 ptemp = tempfile.NamedTemporaryFile()
1527 if isinstance(diff_program, list):
1528 cmd = copy.copy(diff_program)
1529 else:
1530 cmd = [diff_program]
1531 cmd.append(stemp.name)
1532 cmd.append(ttemp.name)
1533 cmd.append(ptemp.name)
1534 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001535 err = []
1536 def run():
1537 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001538 if e:
1539 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001540 th = threading.Thread(target=run)
1541 th.start()
1542 th.join(timeout=300) # 5 mins
1543 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001544 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001545 p.terminate()
1546 th.join(5)
1547 if th.is_alive():
1548 p.kill()
1549 th.join()
1550
Tianjie Xua2a9f992018-01-05 15:15:54 -08001551 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001552 print("WARNING: failure running %s:\n%s\n" % (
1553 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001554 self.patch = None
1555 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001556 diff = ptemp.read()
1557 finally:
1558 ptemp.close()
1559 stemp.close()
1560 ttemp.close()
1561
1562 self.patch = diff
1563 return self.tf, self.sf, self.patch
1564
1565
1566 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001567 """Returns a tuple of (target_file, source_file, patch_data).
1568
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001569 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001570 computing the patch failed.
1571 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001572 return self.tf, self.sf, self.patch
1573
1574
1575def ComputeDifferences(diffs):
1576 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001577 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001578
1579 # Do the largest files first, to try and reduce the long-pole effect.
1580 by_size = [(i.tf.size, i) for i in diffs]
1581 by_size.sort(reverse=True)
1582 by_size = [i[1] for i in by_size]
1583
1584 lock = threading.Lock()
1585 diff_iter = iter(by_size) # accessed under lock
1586
1587 def worker():
1588 try:
1589 lock.acquire()
1590 for d in diff_iter:
1591 lock.release()
1592 start = time.time()
1593 d.ComputePatch()
1594 dur = time.time() - start
1595 lock.acquire()
1596
1597 tf, sf, patch = d.GetPatch()
1598 if sf.name == tf.name:
1599 name = tf.name
1600 else:
1601 name = "%s (%s)" % (tf.name, sf.name)
1602 if patch is None:
Tao Bao76def242017-11-21 09:25:31 -08001603 print(
1604 "patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001605 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001606 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1607 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001608 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001609 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001610 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001611 raise
1612
1613 # start worker threads; wait for them all to finish.
1614 threads = [threading.Thread(target=worker)
1615 for i in range(OPTIONS.worker_threads)]
1616 for th in threads:
1617 th.start()
1618 while threads:
1619 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001620
1621
Dan Albert8b72aef2015-03-23 19:13:21 -07001622class BlockDifference(object):
1623 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001624 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001625 self.tgt = tgt
1626 self.src = src
1627 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001628 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001629 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001630
Tao Baodd2a5892015-03-12 12:32:37 -07001631 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001632 version = max(
1633 int(i) for i in
1634 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001635 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001636 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001637
1638 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001639 version=self.version,
1640 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001641 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001642 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001643 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001644 self.touched_src_ranges = b.touched_src_ranges
1645 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001646
Tao Baoaac4ad52015-10-16 15:26:34 -07001647 if src is None:
1648 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1649 else:
1650 _, self.device = GetTypeAndDevice("/" + partition,
1651 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001652
Tao Baod8d14be2016-02-04 14:26:02 -08001653 @property
1654 def required_cache(self):
1655 return self._required_cache
1656
Tao Bao76def242017-11-21 09:25:31 -08001657 def WriteScript(self, script, output_zip, progress=None,
1658 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001659 if not self.src:
1660 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001661 script.Print("Patching %s image unconditionally..." % (self.partition,))
1662 else:
1663 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001664
Dan Albert8b72aef2015-03-23 19:13:21 -07001665 if progress:
1666 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001667 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001668
1669 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001670 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001671
Tao Bao9bc6bb22015-11-09 16:58:28 -08001672 def WriteStrictVerifyScript(self, script):
1673 """Verify all the blocks in the care_map, including clobbered blocks.
1674
1675 This differs from the WriteVerifyScript() function: a) it prints different
1676 error messages; b) it doesn't allow half-way updated images to pass the
1677 verification."""
1678
1679 partition = self.partition
1680 script.Print("Verifying %s..." % (partition,))
1681 ranges = self.tgt.care_map
1682 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001683 script.AppendExtra(
1684 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1685 'ui_print("\\"%s\\" has unexpected contents.");' % (
1686 self.device, ranges_str,
1687 self.tgt.TotalSha1(include_clobbered_blocks=True),
1688 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001689 script.AppendExtra("")
1690
Tao Baod522bdc2016-04-12 15:53:16 -07001691 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001692 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001693
1694 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001695 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001696 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001697
1698 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001699 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001700 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001701 ranges = self.touched_src_ranges
1702 expected_sha1 = self.touched_src_sha1
1703 else:
1704 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1705 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001706
1707 # No blocks to be checked, skipping.
1708 if not ranges:
1709 return
1710
Tao Bao5ece99d2015-05-12 11:42:31 -07001711 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001712 script.AppendExtra(
1713 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1714 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1715 '"%s.patch.dat")) then' % (
1716 self.device, ranges_str, expected_sha1,
1717 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001718 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001719 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001720
Tianjie Xufc3422a2015-12-15 11:53:59 -08001721 if self.version >= 4:
1722
1723 # Bug: 21124327
1724 # When generating incrementals for the system and vendor partitions in
1725 # version 4 or newer, explicitly check the first block (which contains
1726 # the superblock) of the partition to see if it's what we expect. If
1727 # this check fails, give an explicit log message about the partition
1728 # having been remounted R/W (the most likely explanation).
1729 if self.check_first_block:
1730 script.AppendExtra('check_first_block("%s");' % (self.device,))
1731
1732 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001733 if partition == "system":
1734 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1735 else:
1736 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001737 script.AppendExtra((
1738 'ifelse (block_image_recover("{device}", "{ranges}") && '
1739 'block_image_verify("{device}", '
1740 'package_extract_file("{partition}.transfer.list"), '
1741 '"{partition}.new.dat", "{partition}.patch.dat"), '
1742 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001743 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001744 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001745 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001746
Tao Baodd2a5892015-03-12 12:32:37 -07001747 # Abort the OTA update. Note that the incremental OTA cannot be applied
1748 # even if it may match the checksum of the target partition.
1749 # a) If version < 3, operations like move and erase will make changes
1750 # unconditionally and damage the partition.
1751 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001752 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001753 if partition == "system":
1754 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1755 else:
1756 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1757 script.AppendExtra((
1758 'abort("E%d: %s partition has unexpected contents");\n'
1759 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001760
Tao Bao5fcaaef2015-06-01 13:40:49 -07001761 def _WritePostInstallVerifyScript(self, script):
1762 partition = self.partition
1763 script.Print('Verifying the updated %s image...' % (partition,))
1764 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1765 ranges = self.tgt.care_map
1766 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001767 script.AppendExtra(
1768 'if range_sha1("%s", "%s") == "%s" then' % (
1769 self.device, ranges_str,
1770 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001771
1772 # Bug: 20881595
1773 # Verify that extended blocks are really zeroed out.
1774 if self.tgt.extended:
1775 ranges_str = self.tgt.extended.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._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001780 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001781 if partition == "system":
1782 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1783 else:
1784 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001785 script.AppendExtra(
1786 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001787 ' abort("E%d: %s partition has unexpected non-zero contents after '
1788 'OTA update");\n'
1789 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001790 else:
1791 script.Print('Verified the updated %s image.' % (partition,))
1792
Tianjie Xu209db462016-05-24 17:34:52 -07001793 if partition == "system":
1794 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1795 else:
1796 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1797
Tao Bao5fcaaef2015-06-01 13:40:49 -07001798 script.AppendExtra(
1799 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001800 ' abort("E%d: %s partition has unexpected contents after OTA '
1801 'update");\n'
1802 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001803
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001804 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001805 ZipWrite(output_zip,
1806 '{}.transfer.list'.format(self.path),
1807 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001808
Tao Bao76def242017-11-21 09:25:31 -08001809 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1810 # its size. Quailty 9 almost triples the compression time but doesn't
1811 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001812 # zip | brotli(quality 6) | brotli(quality 9)
1813 # compressed_size: 942M | 869M (~8% reduced) | 854M
1814 # compression_time: 75s | 265s | 719s
1815 # decompression_time: 15s | 25s | 25s
1816
1817 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001818 brotli_cmd = ['brotli', '--quality=6',
1819 '--output={}.new.dat.br'.format(self.path),
1820 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001821 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao80921982018-03-21 21:02:19 -07001822 p = Run(brotli_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1823 stdoutdata, _ = p.communicate()
1824 assert p.returncode == 0, \
1825 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1826 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001827
1828 new_data_name = '{}.new.dat.br'.format(self.partition)
1829 ZipWrite(output_zip,
1830 '{}.new.dat.br'.format(self.path),
1831 new_data_name,
1832 compress_type=zipfile.ZIP_STORED)
1833 else:
1834 new_data_name = '{}.new.dat'.format(self.partition)
1835 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1836
Dan Albert8e0178d2015-01-27 15:53:15 -08001837 ZipWrite(output_zip,
1838 '{}.patch.dat'.format(self.path),
1839 '{}.patch.dat'.format(self.partition),
1840 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001841
Tianjie Xu209db462016-05-24 17:34:52 -07001842 if self.partition == "system":
1843 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1844 else:
1845 code = ErrorCode.VENDOR_UPDATE_FAILURE
1846
Dan Albert8e0178d2015-01-27 15:53:15 -08001847 call = ('block_image_update("{device}", '
1848 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001849 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001850 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001851 device=self.device, partition=self.partition,
1852 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001853 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001854
Dan Albert8b72aef2015-03-23 19:13:21 -07001855 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001856 data = source.ReadRangeSet(ranges)
1857 ctx = sha1()
1858
1859 for p in data:
1860 ctx.update(p)
1861
1862 return ctx.hexdigest()
1863
Tao Baoe9b61912015-07-09 17:37:49 -07001864 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1865 """Return the hash value for all zero blocks."""
1866 zero_block = '\x00' * 4096
1867 ctx = sha1()
1868 for _ in range(num_blocks):
1869 ctx.update(zero_block)
1870
1871 return ctx.hexdigest()
1872
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001873
1874DataImage = blockimgdiff.DataImage
1875
Tao Bao76def242017-11-21 09:25:31 -08001876
Doug Zongker96a57e72010-09-26 14:57:41 -07001877# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001878PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001879 "ext4": "EMMC",
1880 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001881 "f2fs": "EMMC",
1882 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001883}
Doug Zongker96a57e72010-09-26 14:57:41 -07001884
Tao Bao76def242017-11-21 09:25:31 -08001885
Doug Zongker96a57e72010-09-26 14:57:41 -07001886def GetTypeAndDevice(mount_point, info):
1887 fstab = info["fstab"]
1888 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001889 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1890 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001891 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001892 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001893
1894
1895def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001896 """Parses and converts a PEM-encoded certificate into DER-encoded.
1897
1898 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1899
1900 Returns:
1901 The decoded certificate string.
1902 """
1903 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001904 save = False
1905 for line in data.split("\n"):
1906 if "--END CERTIFICATE--" in line:
1907 break
1908 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001909 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001910 if "--BEGIN CERTIFICATE--" in line:
1911 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001912 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001913 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001914
Tao Bao04e1f012018-02-04 12:13:35 -08001915
1916def ExtractPublicKey(cert):
1917 """Extracts the public key (PEM-encoded) from the given certificate file.
1918
1919 Args:
1920 cert: The certificate filename.
1921
1922 Returns:
1923 The public key string.
1924
1925 Raises:
1926 AssertionError: On non-zero return from 'openssl'.
1927 """
1928 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1929 # While openssl 1.1 writes the key into the given filename followed by '-out',
1930 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1931 # stdout instead.
1932 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1933 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1934 pubkey, stderrdata = proc.communicate()
1935 assert proc.returncode == 0, \
1936 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1937 return pubkey
1938
1939
Doug Zongker412c02f2014-02-13 10:58:24 -08001940def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1941 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001942 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001943
Tao Bao6d5d6232018-03-09 17:04:42 -08001944 Most of the space in the boot and recovery images is just the kernel, which is
1945 identical for the two, so the resulting patch should be efficient. Add it to
1946 the output zip, along with a shell script that is run from init.rc on first
1947 boot to actually do the patching and install the new recovery image.
1948
1949 Args:
1950 input_dir: The top-level input directory of the target-files.zip.
1951 output_sink: The callback function that writes the result.
1952 recovery_img: File object for the recovery image.
1953 boot_img: File objects for the boot image.
1954 info_dict: A dict returned by common.LoadInfoDict() on the input
1955 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001956 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001957 if info_dict is None:
1958 info_dict = OPTIONS.info_dict
1959
Tao Bao6d5d6232018-03-09 17:04:42 -08001960 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001961
Tao Baof2cffbd2015-07-22 12:33:18 -07001962 if full_recovery_image:
1963 output_sink("etc/recovery.img", recovery_img.data)
1964
1965 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08001966 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07001967 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08001968 # With system-root-image, boot and recovery images will have mismatching
1969 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
1970 # to handle such a case.
1971 if system_root_image:
1972 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07001973 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08001974 assert not os.path.exists(path)
1975 else:
1976 diff_program = ["imgdiff"]
1977 if os.path.exists(path):
1978 diff_program.append("-b")
1979 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07001980 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08001981 else:
1982 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07001983
1984 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1985 _, _, patch = d.ComputePatch()
1986 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001987
Dan Albertebb19aa2015-03-27 19:11:53 -07001988 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001989 # The following GetTypeAndDevice()s need to use the path in the target
1990 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001991 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1992 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1993 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001994 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001995
Tao Baof2cffbd2015-07-22 12:33:18 -07001996 if full_recovery_image:
1997 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07001998if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
1999 applypatch \\
2000 --flash /system/etc/recovery.img \\
2001 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2002 log -t recovery "Installing new recovery image: succeeded" || \\
2003 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002004else
2005 log -t recovery "Recovery image already installed"
2006fi
2007""" % {'type': recovery_type,
2008 'device': recovery_device,
2009 'sha1': recovery_img.sha1,
2010 'size': recovery_img.size}
2011 else:
2012 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002013if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2014 applypatch %(bonus_args)s \\
2015 --patch /system/recovery-from-boot.p \\
2016 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2017 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2018 log -t recovery "Installing new recovery image: succeeded" || \\
2019 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002020else
2021 log -t recovery "Recovery image already installed"
2022fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002023""" % {'boot_size': boot_img.size,
2024 'boot_sha1': boot_img.sha1,
2025 'recovery_size': recovery_img.size,
2026 'recovery_sha1': recovery_img.sha1,
2027 'boot_type': boot_type,
2028 'boot_device': boot_device,
2029 'recovery_type': recovery_type,
2030 'recovery_device': recovery_device,
2031 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002032
2033 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002034 # in the L release.
2035 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002036
Tao Bao89fbb0f2017-01-10 10:47:58 -08002037 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002038
2039 output_sink(sh_location, sh)