blob: e5438572b07235ea3466e6588df768601cc79a62 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Doug Zongkerea5d7a92010-09-12 15:26:16 -070017import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070018import errno
Doug Zongkereef39442009-04-02 12:14:19 -070019import getopt
20import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010021import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070022import imp
Doug Zongkereef39442009-04-02 12:14:19 -070023import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080024import platform
Doug Zongkereef39442009-04-02 12:14:19 -070025import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070026import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070027import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080028import string
Doug Zongkereef39442009-04-02 12:14:19 -070029import subprocess
30import sys
31import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070032import threading
33import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070034import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080035from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070036
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070037import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080038import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070039
Dan Albert8b72aef2015-03-23 19:13:21 -070040class Options(object):
41 def __init__(self):
42 platform_search_path = {
43 "linux2": "out/host/linux-x86",
44 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070045 }
Doug Zongker85448772014-09-09 14:59:20 -070046
Tao Bao76def242017-11-21 09:25:31 -080047 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080049 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070050 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080052 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070053 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070055 # use otatools built boot_signer by default
56 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070057 self.boot_signer_args = []
58 self.verity_signer_path = None
59 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.verbose = False
61 self.tempfiles = []
62 self.device_specific = None
63 self.extras = {}
64 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070065 self.source_info_dict = None
66 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070068 # Stash size cannot exceed cache_size * threshold.
69 self.cache_size = None
70 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070071
72
73OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070074
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080075
76# Values for "certificate" in apkcerts that mean special things.
77SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
78
Tao Bao9dd909e2017-11-14 11:27:32 -080079
80# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010081AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010082 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080083
84
Tianjie Xu209db462016-05-24 17:34:52 -070085class ErrorCode(object):
86 """Define error_codes for failures that happen during the actual
87 update package installation.
88
89 Error codes 0-999 are reserved for failures before the package
90 installation (i.e. low battery, package verification failure).
91 Detailed code in 'bootable/recovery/error_code.h' """
92
93 SYSTEM_VERIFICATION_FAILURE = 1000
94 SYSTEM_UPDATE_FAILURE = 1001
95 SYSTEM_UNEXPECTED_CONTENTS = 1002
96 SYSTEM_NONZERO_CONTENTS = 1003
97 SYSTEM_RECOVER_FAILURE = 1004
98 VENDOR_VERIFICATION_FAILURE = 2000
99 VENDOR_UPDATE_FAILURE = 2001
100 VENDOR_UNEXPECTED_CONTENTS = 2002
101 VENDOR_NONZERO_CONTENTS = 2003
102 VENDOR_RECOVER_FAILURE = 2004
103 OEM_PROP_MISMATCH = 3000
104 FINGERPRINT_MISMATCH = 3001
105 THUMBPRINT_MISMATCH = 3002
106 OLDER_BUILD = 3003
107 DEVICE_MISMATCH = 3004
108 BAD_PATCH_FILE = 3005
109 INSUFFICIENT_CACHE_SPACE = 3006
110 TUNE_PARTITION_FAILURE = 3007
111 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800112
Tao Bao80921982018-03-21 21:02:19 -0700113
Dan Albert8b72aef2015-03-23 19:13:21 -0700114class ExternalError(RuntimeError):
115 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700116
117
Tao Bao39451582017-05-04 11:10:47 -0700118def Run(args, verbose=None, **kwargs):
119 """Create and return a subprocess.Popen object.
120
121 Caller can specify if the command line should be printed. The global
122 OPTIONS.verbose will be used if not specified.
123 """
124 if verbose is None:
125 verbose = OPTIONS.verbose
126 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800127 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700128 return subprocess.Popen(args, **kwargs)
129
130
Tao Baoc765cca2018-01-31 17:32:40 -0800131def RoundUpTo4K(value):
132 rounded_up = value + 4095
133 return rounded_up - (rounded_up % 4096)
134
135
Ying Wang7e6d4e42010-12-13 16:25:36 -0800136def CloseInheritedPipes():
137 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
138 before doing other work."""
139 if platform.system() != "Darwin":
140 return
141 for d in range(3, 1025):
142 try:
143 stat = os.fstat(d)
144 if stat is not None:
145 pipebit = stat[0] & 0x1000
146 if pipebit != 0:
147 os.close(d)
148 except OSError:
149 pass
150
151
Tao Bao2c15d9e2015-07-09 11:51:16 -0700152def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700153 """Read and parse the META/misc_info.txt key/value pairs from the
154 input target files and return a dict."""
155
Doug Zongkerc9253822014-02-04 12:17:58 -0800156 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700157 if isinstance(input_file, zipfile.ZipFile):
158 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800159 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700160 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800161 try:
162 with open(path) as f:
163 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700164 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800165 if e.errno == errno.ENOENT:
166 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800167
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700168 try:
Michael Runge6e836112014-04-15 17:40:21 -0700169 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700170 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800171 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700172
Tao Bao6cd54732017-02-27 15:12:05 -0800173 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800174 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800175
Tao Bao84e75682015-07-19 02:38:53 -0700176 # A few properties are stored as links to the files in the out/ directory.
177 # It works fine with the build system. However, they are no longer available
178 # when (re)generating from target_files zip. If input_dir is not None, we
179 # are doing repacking. Redirect those properties to the actual files in the
180 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700181 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400182 # We carry a copy of file_contexts.bin under META/. If not available,
183 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700184 # to build images than the one running on device, such as when enabling
185 # system_root_image. In that case, we must have the one for image
186 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700187 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
188 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700189 if d.get("system_root_image") == "true":
190 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700191 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700192 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700193 if not os.path.exists(fc_config):
194 fc_config = None
195
196 if fc_config:
197 d["selinux_fc"] = fc_config
198
Tao Bao8bfd3c72018-07-20 15:20:28 -0700199 # Similarly we need to redirect "root_dir" and "root_fs_config".
Tao Bao84e75682015-07-19 02:38:53 -0700200 if d.get("system_root_image") == "true":
Tao Bao8bfd3c72018-07-20 15:20:28 -0700201 d["root_dir"] = os.path.join(input_dir, "ROOT")
202 d["root_fs_config"] = os.path.join(
Tao Bao84e75682015-07-19 02:38:53 -0700203 input_dir, "META", "root_filesystem_config.txt")
204
Tao Baof54216f2016-03-29 15:12:37 -0700205 # Redirect {system,vendor}_base_fs_file.
206 if "system_base_fs_file" in d:
207 basename = os.path.basename(d["system_base_fs_file"])
208 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700209 if os.path.exists(system_base_fs_file):
210 d["system_base_fs_file"] = system_base_fs_file
211 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800212 print("Warning: failed to find system base fs file: %s" % (
213 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700214 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700215
216 if "vendor_base_fs_file" in d:
217 basename = os.path.basename(d["vendor_base_fs_file"])
218 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700219 if os.path.exists(vendor_base_fs_file):
220 d["vendor_base_fs_file"] = vendor_base_fs_file
221 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800222 print("Warning: failed to find vendor base fs file: %s" % (
223 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700224 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700225
Doug Zongker37974732010-09-16 17:44:38 -0700226 def makeint(key):
227 if key in d:
228 d[key] = int(d[key], 0)
229
230 makeint("recovery_api_version")
231 makeint("blocksize")
232 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700233 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700234 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700235 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700236 makeint("recovery_size")
237 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800238 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700239
Tao Bao76def242017-11-21 09:25:31 -0800240 system_root_image = d.get("system_root_image") == "true"
241 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700242 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800243 d["fstab"] = LoadRecoveryFSTab(
244 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
245 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700246 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800247 d["fstab"] = LoadRecoveryFSTab(
248 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tianjie Xucfa86222016-03-07 16:31:19 -0800249 else:
250 d["fstab"] = None
251
Tao Baobcd1d162017-08-26 13:10:26 -0700252 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
253 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Tao Bao12d87fc2018-01-31 12:18:52 -0800254
255 # Set up the salt (based on fingerprint or thumbprint) that will be used when
256 # adding AVB footer.
257 if d.get("avb_enable") == "true":
258 fp = None
259 if "build.prop" in d:
260 build_prop = d["build.prop"]
261 if "ro.build.fingerprint" in build_prop:
262 fp = build_prop["ro.build.fingerprint"]
263 elif "ro.build.thumbprint" in build_prop:
264 fp = build_prop["ro.build.thumbprint"]
265 if fp:
266 d["avb_salt"] = sha256(fp).hexdigest()
267
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700268 return d
269
Tao Baod1de6f32017-03-01 16:38:48 -0800270
Tao Baobcd1d162017-08-26 13:10:26 -0700271def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700272 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700273 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700274 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700275 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700276 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700277 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700278
Tao Baod1de6f32017-03-01 16:38:48 -0800279
Michael Runge6e836112014-04-15 17:40:21 -0700280def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700281 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700282 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700283 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700284 if not line or line.startswith("#"):
285 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700286 if "=" in line:
287 name, value = line.split("=", 1)
288 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700289 return d
290
Tao Baod1de6f32017-03-01 16:38:48 -0800291
Tianjie Xucfa86222016-03-07 16:31:19 -0800292def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
293 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700294 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800295 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700296 self.mount_point = mount_point
297 self.fs_type = fs_type
298 self.device = device
299 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700300 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700301
302 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800303 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700304 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800305 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700306 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700307
Tao Baod1de6f32017-03-01 16:38:48 -0800308 assert fstab_version == 2
309
310 d = {}
311 for line in data.split("\n"):
312 line = line.strip()
313 if not line or line.startswith("#"):
314 continue
315
316 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
317 pieces = line.split()
318 if len(pieces) != 5:
319 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
320
321 # Ignore entries that are managed by vold.
322 options = pieces[4]
323 if "voldmanaged=" in options:
324 continue
325
326 # It's a good line, parse it.
327 length = 0
328 options = options.split(",")
329 for i in options:
330 if i.startswith("length="):
331 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800332 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800333 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700334 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800335
Tao Baod1de6f32017-03-01 16:38:48 -0800336 mount_flags = pieces[3]
337 # Honor the SELinux context if present.
338 context = None
339 for i in mount_flags.split(","):
340 if i.startswith("context="):
341 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800342
Tao Baod1de6f32017-03-01 16:38:48 -0800343 mount_point = pieces[1]
344 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
345 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800346
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700347 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700348 # system. Other areas assume system is always at "/system" so point /system
349 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700350 if system_root_image:
351 assert not d.has_key("/system") and d.has_key("/")
352 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700353 return d
354
355
Doug Zongker37974732010-09-16 17:44:38 -0700356def DumpInfoDict(d):
357 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800358 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700359
Dan Albert8b72aef2015-03-23 19:13:21 -0700360
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800361def AppendAVBSigningArgs(cmd, partition):
362 """Append signing arguments for avbtool."""
363 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
364 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
365 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
366 if key_path and algorithm:
367 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700368 avb_salt = OPTIONS.info_dict.get("avb_salt")
369 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
370 if avb_salt and partition != "vbmeta":
371 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800372
373
Tao Bao02a08592018-07-22 12:40:45 -0700374def GetAvbChainedPartitionArg(partition, info_dict, key=None):
375 """Constructs and returns the arg to build or verify a chained partition.
376
377 Args:
378 partition: The partition name.
379 info_dict: The info dict to look up the key info and rollback index
380 location.
381 key: The key to be used for building or verifying the partition. Defaults to
382 the key listed in info_dict.
383
384 Returns:
385 A string of form "partition:rollback_index_location:key" that can be used to
386 build or verify vbmeta image.
387
388 Raises:
389 AssertionError: When it fails to extract the public key with avbtool.
390 """
391 if key is None:
392 key = info_dict["avb_" + partition + "_key_path"]
393 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
394 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
395 proc = Run(
396 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path],
397 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
398 stdoutdata, _ = proc.communicate()
399 assert proc.returncode == 0, \
400 "Failed to extract pubkey for {}:\n{}".format(
401 partition, stdoutdata)
402
403 rollback_index_location = info_dict[
404 "avb_" + partition + "_rollback_index_location"]
405 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
406
407
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700408def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800409 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700410 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700411
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700412 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800413 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
414 we are building a two-step special image (i.e. building a recovery image to
415 be loaded into /boot in two-step OTAs).
416
417 Return the image data, or None if sourcedir does not appear to contains files
418 for building the requested image.
419 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700420
421 def make_ramdisk():
422 ramdisk_img = tempfile.NamedTemporaryFile()
423
424 if os.access(fs_config_file, os.F_OK):
425 cmd = ["mkbootfs", "-f", fs_config_file,
426 os.path.join(sourcedir, "RAMDISK")]
427 else:
428 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
429 p1 = Run(cmd, stdout=subprocess.PIPE)
430 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
431
432 p2.wait()
433 p1.wait()
434 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
435 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
436
437 return ramdisk_img
438
439 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
440 return None
441
442 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700443 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700444
Doug Zongkerd5131602012-08-02 14:46:42 -0700445 if info_dict is None:
446 info_dict = OPTIONS.info_dict
447
Doug Zongkereef39442009-04-02 12:14:19 -0700448 img = tempfile.NamedTemporaryFile()
449
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700450 if has_ramdisk:
451 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700452
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800453 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
454 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
455
456 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700457
Benoit Fradina45a8682014-07-14 21:00:43 +0200458 fn = os.path.join(sourcedir, "second")
459 if os.access(fn, os.F_OK):
460 cmd.append("--second")
461 cmd.append(fn)
462
Doug Zongker171f1cd2009-06-15 22:36:37 -0700463 fn = os.path.join(sourcedir, "cmdline")
464 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700465 cmd.append("--cmdline")
466 cmd.append(open(fn).read().rstrip("\n"))
467
468 fn = os.path.join(sourcedir, "base")
469 if os.access(fn, os.F_OK):
470 cmd.append("--base")
471 cmd.append(open(fn).read().rstrip("\n"))
472
Ying Wang4de6b5b2010-08-25 14:29:34 -0700473 fn = os.path.join(sourcedir, "pagesize")
474 if os.access(fn, os.F_OK):
475 cmd.append("--pagesize")
476 cmd.append(open(fn).read().rstrip("\n"))
477
Tao Bao76def242017-11-21 09:25:31 -0800478 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700479 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700480 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700481
Tao Bao76def242017-11-21 09:25:31 -0800482 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000483 if args and args.strip():
484 cmd.extend(shlex.split(args))
485
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700486 if has_ramdisk:
487 cmd.extend(["--ramdisk", ramdisk_img.name])
488
Tao Baod95e9fd2015-03-29 23:07:41 -0700489 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800490 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700491 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700492 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700493 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700494 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700495
Tao Baobf70c3182017-07-11 17:27:55 -0700496 # "boot" or "recovery", without extension.
497 partition_name = os.path.basename(sourcedir).lower()
498
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700499 if (partition_name == "recovery" and
500 info_dict.get("include_recovery_dtbo") == "true"):
501 fn = os.path.join(sourcedir, "recovery_dtbo")
502 cmd.extend(["--recovery_dtbo", fn])
503
Doug Zongker38a649f2009-06-17 09:07:09 -0700504 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700505 p.communicate()
Tao Baobf70c3182017-07-11 17:27:55 -0700506 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700507
Tao Bao76def242017-11-21 09:25:31 -0800508 if (info_dict.get("boot_signer") == "true" and
509 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800510 # Hard-code the path as "/boot" for two-step special recovery image (which
511 # will be loaded into /boot during the two-step OTA).
512 if two_step_image:
513 path = "/boot"
514 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700515 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700516 cmd = [OPTIONS.boot_signer_path]
517 cmd.extend(OPTIONS.boot_signer_args)
518 cmd.extend([path, img.name,
519 info_dict["verity_key"] + ".pk8",
520 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700521 p = Run(cmd, stdout=subprocess.PIPE)
522 p.communicate()
523 assert p.returncode == 0, "boot_signer of %s image failed" % path
524
Tao Baod95e9fd2015-03-29 23:07:41 -0700525 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800526 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700527 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700528 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800529 # We have switched from the prebuilt futility binary to using the tool
530 # (futility-host) built from the source. Override the setting in the old
531 # TF.zip.
532 futility = info_dict["futility"]
533 if futility.startswith("prebuilts/"):
534 futility = "futility-host"
535 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700536 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700537 info_dict["vboot_key"] + ".vbprivk",
538 info_dict["vboot_subkey"] + ".vbprivk",
539 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700540 img.name]
541 p = Run(cmd, stdout=subprocess.PIPE)
542 p.communicate()
543 assert p.returncode == 0, "vboot_signer of %s image failed" % path
544
Tao Baof3282b42015-04-01 11:21:55 -0700545 # Clean up the temp files.
546 img_unsigned.close()
547 img_keyblock.close()
548
David Zeuthen8fecb282017-12-01 16:24:01 -0500549 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800550 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700551 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500552 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400553 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700554 "--partition_size", str(part_size), "--partition_name",
555 partition_name]
556 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500557 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400558 if args and args.strip():
559 cmd.extend(shlex.split(args))
560 p = Run(cmd, stdout=subprocess.PIPE)
561 p.communicate()
562 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c3182017-07-11 17:27:55 -0700563 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500564
565 img.seek(os.SEEK_SET, 0)
566 data = img.read()
567
568 if has_ramdisk:
569 ramdisk_img.close()
570 img.close()
571
572 return data
573
574
Doug Zongkerd5131602012-08-02 14:46:42 -0700575def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800576 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700577 """Return a File object with the desired bootable image.
578
579 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
580 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
581 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700582
Doug Zongker55d93282011-01-25 17:03:34 -0800583 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
584 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800585 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800586 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700587
588 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
589 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800590 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700591 return File.FromLocalFile(name, prebuilt_path)
592
Tao Bao89fbb0f2017-01-10 10:47:58 -0800593 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700594
595 if info_dict is None:
596 info_dict = OPTIONS.info_dict
597
598 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800599 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
600 # for recovery.
601 has_ramdisk = (info_dict.get("system_root_image") != "true" or
602 prebuilt_name != "boot.img" or
603 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700604
Doug Zongker6f1d0312014-08-22 08:07:12 -0700605 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400606 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
607 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800608 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700609 if data:
610 return File(name, data)
611 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800612
Doug Zongkereef39442009-04-02 12:14:19 -0700613
Narayan Kamatha07bf042017-08-14 14:49:21 +0100614def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800615 """Gunzips the given gzip compressed file to a given output file."""
616 with gzip.open(in_filename, "rb") as in_file, \
617 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100618 shutil.copyfileobj(in_file, out_file)
619
620
Doug Zongker75f17362009-12-08 13:46:44 -0800621def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800622 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800623
Tao Bao1c830bf2017-12-25 10:43:47 -0800624 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
625 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800626
Tao Bao1c830bf2017-12-25 10:43:47 -0800627 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800628 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800629 """
Doug Zongkereef39442009-04-02 12:14:19 -0700630
Doug Zongker55d93282011-01-25 17:03:34 -0800631 def unzip_to_dir(filename, dirname):
632 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
633 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800634 cmd.extend(pattern)
Tao Bao80921982018-03-21 21:02:19 -0700635 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
636 stdoutdata, _ = p.communicate()
Doug Zongker55d93282011-01-25 17:03:34 -0800637 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700638 raise ExternalError(
639 "Failed to unzip input target-files \"{}\":\n{}".format(
640 filename, stdoutdata))
Doug Zongker55d93282011-01-25 17:03:34 -0800641
Tao Bao1c830bf2017-12-25 10:43:47 -0800642 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800643 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
644 if m:
645 unzip_to_dir(m.group(1), tmp)
646 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
647 filename = m.group(1)
648 else:
649 unzip_to_dir(filename, tmp)
650
Tao Baodba59ee2018-01-09 13:21:02 -0800651 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700652
653
Tao Baoe709b092018-02-07 12:40:00 -0800654def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -0800655 """Returns a SparseImage object suitable for passing to BlockImageDiff.
656
657 This function loads the specified sparse image from the given path, and
658 performs additional processing for OTA purpose. For example, it always adds
659 block 0 to clobbered blocks list. It also detects files that cannot be
660 reconstructed from the block list, for whom we should avoid applying imgdiff.
661
662 Args:
663 which: The partition name, which must be "system" or "vendor".
664 tmpdir: The directory that contains the prebuilt image and block map file.
665 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800666 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -0800667
668 Returns:
669 A SparseImage object, with file_map info loaded.
670 """
671 assert which in ("system", "vendor")
672
673 path = os.path.join(tmpdir, "IMAGES", which + ".img")
674 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
675
676 # The image and map files must have been created prior to calling
677 # ota_from_target_files.py (since LMP).
678 assert os.path.exists(path) and os.path.exists(mappath)
679
680 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
681 # it to clobbered_blocks so that it will be written to the target
682 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
683 clobbered_blocks = "0"
684
Tao Baoe709b092018-02-07 12:40:00 -0800685 image = sparse_img.SparseImage(path, mappath, clobbered_blocks,
686 allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -0800687
688 # block.map may contain less blocks, because mke2fs may skip allocating blocks
689 # if they contain all zeros. We can't reconstruct such a file from its block
690 # list. Tag such entries accordingly. (Bug: 65213616)
691 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800692 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700693 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800694 continue
695
Tao Baod3554e62018-07-10 15:31:22 -0700696 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that when
697 # using system_root_image, the filename listed in system.map may contain an
698 # additional leading slash (i.e. "//system/framework/am.jar"). Using lstrip
699 # to get consistent results.
700 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
701
702 # Special handling another case with system_root_image, where files not
703 # under /system (e.g. "/sbin/charger") are packed under ROOT/ in a
704 # target_files.zip.
705 if which == 'system' and not arcname.startswith('SYSTEM'):
706 arcname = 'ROOT/' + arcname
707
708 assert arcname in input_zip.namelist(), \
709 "Failed to find the ZIP entry for {}".format(entry)
710
Tao Baoc765cca2018-01-31 17:32:40 -0800711 info = input_zip.getinfo(arcname)
712 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800713
714 # If a RangeSet has been tagged as using shared blocks while loading the
715 # image, its block list must be already incomplete due to that reason. Don't
716 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
717 if ranges.extra.get('uses_shared_blocks'):
718 continue
719
Tao Baoc765cca2018-01-31 17:32:40 -0800720 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
721 ranges.extra['incomplete'] = True
722
723 return image
724
725
Doug Zongkereef39442009-04-02 12:14:19 -0700726def GetKeyPasswords(keylist):
727 """Given a list of keys, prompt the user to enter passwords for
728 those which require them. Return a {key: password} dict. password
729 will be None if the key has no password."""
730
Doug Zongker8ce7c252009-05-22 13:34:54 -0700731 no_passwords = []
732 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700733 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700734 devnull = open("/dev/null", "w+b")
735 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800736 # We don't need a password for things that aren't really keys.
737 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700738 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700739 continue
740
T.R. Fullhart37e10522013-03-18 10:31:26 -0700741 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700742 "-inform", "DER", "-nocrypt"],
743 stdin=devnull.fileno(),
744 stdout=devnull.fileno(),
745 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700746 p.communicate()
747 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700748 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700749 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700750 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700751 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
752 "-inform", "DER", "-passin", "pass:"],
753 stdin=devnull.fileno(),
754 stdout=devnull.fileno(),
755 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700756 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700757 if p.returncode == 0:
758 # Encrypted key with empty string as password.
759 key_passwords[k] = ''
760 elif stderr.startswith('Error decrypting key'):
761 # Definitely encrypted key.
762 # It would have said "Error reading key" if it didn't parse correctly.
763 need_passwords.append(k)
764 else:
765 # Potentially, a type of key that openssl doesn't understand.
766 # We'll let the routines in signapk.jar handle it.
767 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700768 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700769
T.R. Fullhart37e10522013-03-18 10:31:26 -0700770 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800771 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700772 return key_passwords
773
774
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800775def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700776 """Gets the minSdkVersion declared in the APK.
777
778 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
779 This can be both a decimal number (API Level) or a codename.
780
781 Args:
782 apk_name: The APK filename.
783
784 Returns:
785 The parsed SDK version string.
786
787 Raises:
788 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800789 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700790 proc = Run(
791 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
792 stderr=subprocess.PIPE)
793 stdoutdata, stderrdata = proc.communicate()
794 if proc.returncode != 0:
795 raise ExternalError(
796 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
797 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800798
Tao Baof47bf0f2018-03-21 23:28:51 -0700799 for line in stdoutdata.split("\n"):
800 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800801 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
802 if m:
803 return m.group(1)
804 raise ExternalError("No minSdkVersion returned by aapt")
805
806
807def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700808 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800809
Tao Baof47bf0f2018-03-21 23:28:51 -0700810 If minSdkVersion is set to a codename, it is translated to a number using the
811 provided map.
812
813 Args:
814 apk_name: The APK filename.
815
816 Returns:
817 The parsed SDK version number.
818
819 Raises:
820 ExternalError: On failing to get the min SDK version number.
821 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800822 version = GetMinSdkVersion(apk_name)
823 try:
824 return int(version)
825 except ValueError:
826 # Not a decimal number. Codename?
827 if version in codename_to_api_level_map:
828 return codename_to_api_level_map[version]
829 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700830 raise ExternalError(
831 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
832 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800833
834
835def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800836 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700837 """Sign the input_name zip/jar/apk, producing output_name. Use the
838 given key and password (the latter may be None if the key does not
839 have a password.
840
Doug Zongker951495f2009-08-14 12:44:19 -0700841 If whole_file is true, use the "-w" option to SignApk to embed a
842 signature that covers the whole file in the archive comment of the
843 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800844
845 min_api_level is the API Level (int) of the oldest platform this file may end
846 up on. If not specified for an APK, the API Level is obtained by interpreting
847 the minSdkVersion attribute of the APK's AndroidManifest.xml.
848
849 codename_to_api_level_map is needed to translate the codename which may be
850 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700851 """
Tao Bao76def242017-11-21 09:25:31 -0800852 if codename_to_api_level_map is None:
853 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700854
Alex Klyubin9667b182015-12-10 13:38:50 -0800855 java_library_path = os.path.join(
856 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
857
Tao Baoe95540e2016-11-08 12:08:53 -0800858 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
859 ["-Djava.library.path=" + java_library_path,
860 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
861 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700862 if whole_file:
863 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800864
865 min_sdk_version = min_api_level
866 if min_sdk_version is None:
867 if not whole_file:
868 min_sdk_version = GetMinSdkVersionInt(
869 input_name, codename_to_api_level_map)
870 if min_sdk_version is not None:
871 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
872
T.R. Fullhart37e10522013-03-18 10:31:26 -0700873 cmd.extend([key + OPTIONS.public_key_suffix,
874 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800875 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700876
Tao Bao80921982018-03-21 21:02:19 -0700877 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
878 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700879 if password is not None:
880 password += "\n"
Tao Bao80921982018-03-21 21:02:19 -0700881 stdoutdata, _ = p.communicate(password)
Doug Zongkereef39442009-04-02 12:14:19 -0700882 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700883 raise ExternalError(
884 "Failed to run signapk.jar: return code {}:\n{}".format(
885 p.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700886
Doug Zongkereef39442009-04-02 12:14:19 -0700887
Doug Zongker37974732010-09-16 17:44:38 -0700888def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800889 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700890
Tao Bao9dd909e2017-11-14 11:27:32 -0800891 For non-AVB images, raise exception if the data is too big. Print a warning
892 if the data is nearing the maximum size.
893
894 For AVB images, the actual image size should be identical to the limit.
895
896 Args:
897 data: A string that contains all the data for the partition.
898 target: The partition name. The ".img" suffix is optional.
899 info_dict: The dict to be looked up for relevant info.
900 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700901 if target.endswith(".img"):
902 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700903 mount_point = "/" + target
904
Ying Wangf8824af2014-06-03 14:07:27 -0700905 fs_type = None
906 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700907 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700908 if mount_point == "/userdata":
909 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700910 p = info_dict["fstab"][mount_point]
911 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800912 device = p.device
913 if "/" in device:
914 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -0800915 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -0700916 if not fs_type or not limit:
917 return
Doug Zongkereef39442009-04-02 12:14:19 -0700918
Andrew Boie0f9aec82012-02-14 09:32:52 -0800919 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800920 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
921 # path.
922 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
923 if size != limit:
924 raise ExternalError(
925 "Mismatching image size for %s: expected %d actual %d" % (
926 target, limit, size))
927 else:
928 pct = float(size) * 100.0 / limit
929 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
930 if pct >= 99.0:
931 raise ExternalError(msg)
932 elif pct >= 95.0:
933 print("\n WARNING: %s\n" % (msg,))
934 elif OPTIONS.verbose:
935 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700936
937
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800938def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800939 """Parses the APK certs info from a given target-files zip.
940
941 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
942 tuple with the following elements: (1) a dictionary that maps packages to
943 certs (based on the "certificate" and "private_key" attributes in the file;
944 (2) a string representing the extension of compressed APKs in the target files
945 (e.g ".gz", ".bro").
946
947 Args:
948 tf_zip: The input target_files ZipFile (already open).
949
950 Returns:
951 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
952 the extension string of compressed APKs (e.g. ".gz"), or None if there's
953 no compressed APKs.
954 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800955 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100956 compressed_extension = None
957
Tao Bao0f990332017-09-08 19:02:54 -0700958 # META/apkcerts.txt contains the info for _all_ the packages known at build
959 # time. Filter out the ones that are not installed.
960 installed_files = set()
961 for name in tf_zip.namelist():
962 basename = os.path.basename(name)
963 if basename:
964 installed_files.add(basename)
965
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800966 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
967 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700968 if not line:
969 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800970 m = re.match(
971 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
972 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
973 line)
974 if not m:
975 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100976
Tao Bao818ddf52018-01-05 11:17:34 -0800977 matches = m.groupdict()
978 cert = matches["CERT"]
979 privkey = matches["PRIVKEY"]
980 name = matches["NAME"]
981 this_compressed_extension = matches["COMPRESSED"]
982
983 public_key_suffix_len = len(OPTIONS.public_key_suffix)
984 private_key_suffix_len = len(OPTIONS.private_key_suffix)
985 if cert in SPECIAL_CERT_STRINGS and not privkey:
986 certmap[name] = cert
987 elif (cert.endswith(OPTIONS.public_key_suffix) and
988 privkey.endswith(OPTIONS.private_key_suffix) and
989 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
990 certmap[name] = cert[:-public_key_suffix_len]
991 else:
992 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
993
994 if not this_compressed_extension:
995 continue
996
997 # Only count the installed files.
998 filename = name + '.' + this_compressed_extension
999 if filename not in installed_files:
1000 continue
1001
1002 # Make sure that all the values in the compression map have the same
1003 # extension. We don't support multiple compression methods in the same
1004 # system image.
1005 if compressed_extension:
1006 if this_compressed_extension != compressed_extension:
1007 raise ValueError(
1008 "Multiple compressed extensions: {} vs {}".format(
1009 compressed_extension, this_compressed_extension))
1010 else:
1011 compressed_extension = this_compressed_extension
1012
1013 return (certmap,
1014 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001015
1016
Doug Zongkereef39442009-04-02 12:14:19 -07001017COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001018Global options
1019
1020 -p (--path) <dir>
1021 Prepend <dir>/bin to the list of places to search for binaries run by this
1022 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001023
Doug Zongker05d3dea2009-06-22 11:32:31 -07001024 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001025 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001026
Tao Bao30df8b42018-04-23 15:32:53 -07001027 -x (--extra) <key=value>
1028 Add a key/value pair to the 'extras' dict, which device-specific extension
1029 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001030
Doug Zongkereef39442009-04-02 12:14:19 -07001031 -v (--verbose)
1032 Show command lines being executed.
1033
1034 -h (--help)
1035 Display this usage message and exit.
1036"""
1037
1038def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001039 print(docstring.rstrip("\n"))
1040 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001041
1042
1043def ParseOptions(argv,
1044 docstring,
1045 extra_opts="", extra_long_opts=(),
1046 extra_option_handler=None):
1047 """Parse the options in argv and return any arguments that aren't
1048 flags. docstring is the calling module's docstring, to be displayed
1049 for errors and -h. extra_opts and extra_long_opts are for flags
1050 defined by the caller, which are processed by passing them to
1051 extra_option_handler."""
1052
1053 try:
1054 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001055 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001056 ["help", "verbose", "path=", "signapk_path=",
1057 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001058 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001059 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1060 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001061 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001062 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001063 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001064 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001065 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001066 sys.exit(2)
1067
Doug Zongkereef39442009-04-02 12:14:19 -07001068 for o, a in opts:
1069 if o in ("-h", "--help"):
1070 Usage(docstring)
1071 sys.exit()
1072 elif o in ("-v", "--verbose"):
1073 OPTIONS.verbose = True
1074 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001075 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001076 elif o in ("--signapk_path",):
1077 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001078 elif o in ("--signapk_shared_library_path",):
1079 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001080 elif o in ("--extra_signapk_args",):
1081 OPTIONS.extra_signapk_args = shlex.split(a)
1082 elif o in ("--java_path",):
1083 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001084 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001085 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001086 elif o in ("--public_key_suffix",):
1087 OPTIONS.public_key_suffix = a
1088 elif o in ("--private_key_suffix",):
1089 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001090 elif o in ("--boot_signer_path",):
1091 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001092 elif o in ("--boot_signer_args",):
1093 OPTIONS.boot_signer_args = shlex.split(a)
1094 elif o in ("--verity_signer_path",):
1095 OPTIONS.verity_signer_path = a
1096 elif o in ("--verity_signer_args",):
1097 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001098 elif o in ("-s", "--device_specific"):
1099 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001100 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001101 key, value = a.split("=", 1)
1102 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001103 else:
1104 if extra_option_handler is None or not extra_option_handler(o, a):
1105 assert False, "unknown option \"%s\"" % (o,)
1106
Doug Zongker85448772014-09-09 14:59:20 -07001107 if OPTIONS.search_path:
1108 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1109 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001110
1111 return args
1112
1113
Tao Bao4c851b12016-09-19 13:54:38 -07001114def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001115 """Make a temp file and add it to the list of things to be deleted
1116 when Cleanup() is called. Return the filename."""
1117 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1118 os.close(fd)
1119 OPTIONS.tempfiles.append(fn)
1120 return fn
1121
1122
Tao Bao1c830bf2017-12-25 10:43:47 -08001123def MakeTempDir(prefix='tmp', suffix=''):
1124 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1125
1126 Returns:
1127 The absolute pathname of the new directory.
1128 """
1129 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1130 OPTIONS.tempfiles.append(dir_name)
1131 return dir_name
1132
1133
Doug Zongkereef39442009-04-02 12:14:19 -07001134def Cleanup():
1135 for i in OPTIONS.tempfiles:
1136 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001137 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001138 else:
1139 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001140 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001141
1142
1143class PasswordManager(object):
1144 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001145 self.editor = os.getenv("EDITOR")
1146 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001147
1148 def GetPasswords(self, items):
1149 """Get passwords corresponding to each string in 'items',
1150 returning a dict. (The dict may have keys in addition to the
1151 values in 'items'.)
1152
1153 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1154 user edit that file to add more needed passwords. If no editor is
1155 available, or $ANDROID_PW_FILE isn't define, prompts the user
1156 interactively in the ordinary way.
1157 """
1158
1159 current = self.ReadFile()
1160
1161 first = True
1162 while True:
1163 missing = []
1164 for i in items:
1165 if i not in current or not current[i]:
1166 missing.append(i)
1167 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001168 if not missing:
1169 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001170
1171 for i in missing:
1172 current[i] = ""
1173
1174 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001175 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001176 answer = raw_input("try to edit again? [y]> ").strip()
1177 if answer and answer[0] not in 'yY':
1178 raise RuntimeError("key passwords unavailable")
1179 first = False
1180
1181 current = self.UpdateAndReadFile(current)
1182
Dan Albert8b72aef2015-03-23 19:13:21 -07001183 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001184 """Prompt the user to enter a value (password) for each key in
1185 'current' whose value is fales. Returns a new dict with all the
1186 values.
1187 """
1188 result = {}
1189 for k, v in sorted(current.iteritems()):
1190 if v:
1191 result[k] = v
1192 else:
1193 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001194 result[k] = getpass.getpass(
1195 "Enter password for %s key> " % k).strip()
1196 if result[k]:
1197 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001198 return result
1199
1200 def UpdateAndReadFile(self, current):
1201 if not self.editor or not self.pwfile:
1202 return self.PromptResult(current)
1203
1204 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001205 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001206 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1207 f.write("# (Additional spaces are harmless.)\n\n")
1208
1209 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001210 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1211 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001212 f.write("[[[ %s ]]] %s\n" % (v, k))
1213 if not v and first_line is None:
1214 # position cursor on first line with no password.
1215 first_line = i + 4
1216 f.close()
1217
1218 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1219 _, _ = p.communicate()
1220
1221 return self.ReadFile()
1222
1223 def ReadFile(self):
1224 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001225 if self.pwfile is None:
1226 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001227 try:
1228 f = open(self.pwfile, "r")
1229 for line in f:
1230 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001231 if not line or line[0] == '#':
1232 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001233 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1234 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001235 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001236 else:
1237 result[m.group(2)] = m.group(1)
1238 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001239 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001240 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001241 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001242 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001243
1244
Dan Albert8e0178d2015-01-27 15:53:15 -08001245def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1246 compress_type=None):
1247 import datetime
1248
1249 # http://b/18015246
1250 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1251 # for files larger than 2GiB. We can work around this by adjusting their
1252 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1253 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1254 # it isn't clear to me exactly what circumstances cause this).
1255 # `zipfile.write()` must be used directly to work around this.
1256 #
1257 # This mess can be avoided if we port to python3.
1258 saved_zip64_limit = zipfile.ZIP64_LIMIT
1259 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1260
1261 if compress_type is None:
1262 compress_type = zip_file.compression
1263 if arcname is None:
1264 arcname = filename
1265
1266 saved_stat = os.stat(filename)
1267
1268 try:
1269 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1270 # file to be zipped and reset it when we're done.
1271 os.chmod(filename, perms)
1272
1273 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001274 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1275 # intentional. zip stores datetimes in local time without a time zone
1276 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1277 # in the zip archive.
1278 local_epoch = datetime.datetime.fromtimestamp(0)
1279 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001280 os.utime(filename, (timestamp, timestamp))
1281
1282 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1283 finally:
1284 os.chmod(filename, saved_stat.st_mode)
1285 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1286 zipfile.ZIP64_LIMIT = saved_zip64_limit
1287
1288
Tao Bao58c1b962015-05-20 09:32:18 -07001289def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001290 compress_type=None):
1291 """Wrap zipfile.writestr() function to work around the zip64 limit.
1292
1293 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1294 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1295 when calling crc32(bytes).
1296
1297 But it still works fine to write a shorter string into a large zip file.
1298 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1299 when we know the string won't be too long.
1300 """
1301
1302 saved_zip64_limit = zipfile.ZIP64_LIMIT
1303 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1304
1305 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1306 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001307 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001308 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001309 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001310 else:
Tao Baof3282b42015-04-01 11:21:55 -07001311 zinfo = zinfo_or_arcname
1312
1313 # If compress_type is given, it overrides the value in zinfo.
1314 if compress_type is not None:
1315 zinfo.compress_type = compress_type
1316
Tao Bao58c1b962015-05-20 09:32:18 -07001317 # If perms is given, it has a priority.
1318 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001319 # If perms doesn't set the file type, mark it as a regular file.
1320 if perms & 0o770000 == 0:
1321 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001322 zinfo.external_attr = perms << 16
1323
Tao Baof3282b42015-04-01 11:21:55 -07001324 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001325 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1326
Dan Albert8b72aef2015-03-23 19:13:21 -07001327 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001328 zipfile.ZIP64_LIMIT = saved_zip64_limit
1329
1330
Tao Bao89d7ab22017-12-14 17:05:33 -08001331def ZipDelete(zip_filename, entries):
1332 """Deletes entries from a ZIP file.
1333
1334 Since deleting entries from a ZIP file is not supported, it shells out to
1335 'zip -d'.
1336
1337 Args:
1338 zip_filename: The name of the ZIP file.
1339 entries: The name of the entry, or the list of names to be deleted.
1340
1341 Raises:
1342 AssertionError: In case of non-zero return from 'zip'.
1343 """
1344 if isinstance(entries, basestring):
1345 entries = [entries]
1346 cmd = ["zip", "-d", zip_filename] + entries
1347 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1348 stdoutdata, _ = proc.communicate()
1349 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1350 stdoutdata)
1351
1352
Tao Baof3282b42015-04-01 11:21:55 -07001353def ZipClose(zip_file):
1354 # http://b/18015246
1355 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1356 # central directory.
1357 saved_zip64_limit = zipfile.ZIP64_LIMIT
1358 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1359
1360 zip_file.close()
1361
1362 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001363
1364
1365class DeviceSpecificParams(object):
1366 module = None
1367 def __init__(self, **kwargs):
1368 """Keyword arguments to the constructor become attributes of this
1369 object, which is passed to all functions in the device-specific
1370 module."""
1371 for k, v in kwargs.iteritems():
1372 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001373 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001374
1375 if self.module is None:
1376 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001377 if not path:
1378 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001379 try:
1380 if os.path.isdir(path):
1381 info = imp.find_module("releasetools", [path])
1382 else:
1383 d, f = os.path.split(path)
1384 b, x = os.path.splitext(f)
1385 if x == ".py":
1386 f = b
1387 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001388 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001389 self.module = imp.load_module("device_specific", *info)
1390 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001391 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001392
1393 def _DoCall(self, function_name, *args, **kwargs):
1394 """Call the named function in the device-specific module, passing
1395 the given args and kwargs. The first argument to the call will be
1396 the DeviceSpecific object itself. If there is no module, or the
1397 module does not define the function, return the value of the
1398 'default' kwarg (which itself defaults to None)."""
1399 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001400 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001401 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1402
1403 def FullOTA_Assertions(self):
1404 """Called after emitting the block of assertions at the top of a
1405 full OTA package. Implementations can add whatever additional
1406 assertions they like."""
1407 return self._DoCall("FullOTA_Assertions")
1408
Doug Zongkere5ff5902012-01-17 10:55:37 -08001409 def FullOTA_InstallBegin(self):
1410 """Called at the start of full OTA installation."""
1411 return self._DoCall("FullOTA_InstallBegin")
1412
Doug Zongker05d3dea2009-06-22 11:32:31 -07001413 def FullOTA_InstallEnd(self):
1414 """Called at the end of full OTA installation; typically this is
1415 used to install the image for the device's baseband processor."""
1416 return self._DoCall("FullOTA_InstallEnd")
1417
1418 def IncrementalOTA_Assertions(self):
1419 """Called after emitting the block of assertions at the top of an
1420 incremental OTA package. Implementations can add whatever
1421 additional assertions they like."""
1422 return self._DoCall("IncrementalOTA_Assertions")
1423
Doug Zongkere5ff5902012-01-17 10:55:37 -08001424 def IncrementalOTA_VerifyBegin(self):
1425 """Called at the start of the verification phase of incremental
1426 OTA installation; additional checks can be placed here to abort
1427 the script before any changes are made."""
1428 return self._DoCall("IncrementalOTA_VerifyBegin")
1429
Doug Zongker05d3dea2009-06-22 11:32:31 -07001430 def IncrementalOTA_VerifyEnd(self):
1431 """Called at the end of the verification phase of incremental OTA
1432 installation; additional checks can be placed here to abort the
1433 script before any changes are made."""
1434 return self._DoCall("IncrementalOTA_VerifyEnd")
1435
Doug Zongkere5ff5902012-01-17 10:55:37 -08001436 def IncrementalOTA_InstallBegin(self):
1437 """Called at the start of incremental OTA installation (after
1438 verification is complete)."""
1439 return self._DoCall("IncrementalOTA_InstallBegin")
1440
Doug Zongker05d3dea2009-06-22 11:32:31 -07001441 def IncrementalOTA_InstallEnd(self):
1442 """Called at the end of incremental OTA installation; typically
1443 this is used to install the image for the device's baseband
1444 processor."""
1445 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001446
Tao Bao9bc6bb22015-11-09 16:58:28 -08001447 def VerifyOTA_Assertions(self):
1448 return self._DoCall("VerifyOTA_Assertions")
1449
Tao Bao76def242017-11-21 09:25:31 -08001450
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001451class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001452 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001453 self.name = name
1454 self.data = data
1455 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001456 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001457 self.sha1 = sha1(data).hexdigest()
1458
1459 @classmethod
1460 def FromLocalFile(cls, name, diskname):
1461 f = open(diskname, "rb")
1462 data = f.read()
1463 f.close()
1464 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001465
1466 def WriteToTemp(self):
1467 t = tempfile.NamedTemporaryFile()
1468 t.write(self.data)
1469 t.flush()
1470 return t
1471
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001472 def WriteToDir(self, d):
1473 with open(os.path.join(d, self.name), "wb") as fp:
1474 fp.write(self.data)
1475
Geremy Condra36bd3652014-02-06 19:45:10 -08001476 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001477 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001478
Tao Bao76def242017-11-21 09:25:31 -08001479
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001480DIFF_PROGRAM_BY_EXT = {
1481 ".gz" : "imgdiff",
1482 ".zip" : ["imgdiff", "-z"],
1483 ".jar" : ["imgdiff", "-z"],
1484 ".apk" : ["imgdiff", "-z"],
1485 ".img" : "imgdiff",
1486 }
1487
Tao Bao76def242017-11-21 09:25:31 -08001488
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001489class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001490 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001491 self.tf = tf
1492 self.sf = sf
1493 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001494 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001495
1496 def ComputePatch(self):
1497 """Compute the patch (as a string of data) needed to turn sf into
1498 tf. Returns the same tuple as GetPatch()."""
1499
1500 tf = self.tf
1501 sf = self.sf
1502
Doug Zongker24cd2802012-08-14 16:36:15 -07001503 if self.diff_program:
1504 diff_program = self.diff_program
1505 else:
1506 ext = os.path.splitext(tf.name)[1]
1507 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001508
1509 ttemp = tf.WriteToTemp()
1510 stemp = sf.WriteToTemp()
1511
1512 ext = os.path.splitext(tf.name)[1]
1513
1514 try:
1515 ptemp = tempfile.NamedTemporaryFile()
1516 if isinstance(diff_program, list):
1517 cmd = copy.copy(diff_program)
1518 else:
1519 cmd = [diff_program]
1520 cmd.append(stemp.name)
1521 cmd.append(ttemp.name)
1522 cmd.append(ptemp.name)
1523 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001524 err = []
1525 def run():
1526 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001527 if e:
1528 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001529 th = threading.Thread(target=run)
1530 th.start()
1531 th.join(timeout=300) # 5 mins
1532 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001533 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001534 p.terminate()
1535 th.join(5)
1536 if th.is_alive():
1537 p.kill()
1538 th.join()
1539
Tianjie Xua2a9f992018-01-05 15:15:54 -08001540 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001541 print("WARNING: failure running %s:\n%s\n" % (
1542 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001543 self.patch = None
1544 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001545 diff = ptemp.read()
1546 finally:
1547 ptemp.close()
1548 stemp.close()
1549 ttemp.close()
1550
1551 self.patch = diff
1552 return self.tf, self.sf, self.patch
1553
1554
1555 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001556 """Returns a tuple of (target_file, source_file, patch_data).
1557
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001558 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001559 computing the patch failed.
1560 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001561 return self.tf, self.sf, self.patch
1562
1563
1564def ComputeDifferences(diffs):
1565 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001566 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001567
1568 # Do the largest files first, to try and reduce the long-pole effect.
1569 by_size = [(i.tf.size, i) for i in diffs]
1570 by_size.sort(reverse=True)
1571 by_size = [i[1] for i in by_size]
1572
1573 lock = threading.Lock()
1574 diff_iter = iter(by_size) # accessed under lock
1575
1576 def worker():
1577 try:
1578 lock.acquire()
1579 for d in diff_iter:
1580 lock.release()
1581 start = time.time()
1582 d.ComputePatch()
1583 dur = time.time() - start
1584 lock.acquire()
1585
1586 tf, sf, patch = d.GetPatch()
1587 if sf.name == tf.name:
1588 name = tf.name
1589 else:
1590 name = "%s (%s)" % (tf.name, sf.name)
1591 if patch is None:
Tao Bao76def242017-11-21 09:25:31 -08001592 print(
1593 "patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001594 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001595 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1596 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001597 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001598 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001599 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001600 raise
1601
1602 # start worker threads; wait for them all to finish.
1603 threads = [threading.Thread(target=worker)
1604 for i in range(OPTIONS.worker_threads)]
1605 for th in threads:
1606 th.start()
1607 while threads:
1608 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001609
1610
Dan Albert8b72aef2015-03-23 19:13:21 -07001611class BlockDifference(object):
1612 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001613 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001614 self.tgt = tgt
1615 self.src = src
1616 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001617 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001618 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001619
Tao Baodd2a5892015-03-12 12:32:37 -07001620 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001621 version = max(
1622 int(i) for i in
1623 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001624 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001625 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001626
1627 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001628 version=self.version,
1629 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001630 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001631 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001632 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001633 self.touched_src_ranges = b.touched_src_ranges
1634 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001635
Tao Baoaac4ad52015-10-16 15:26:34 -07001636 if src is None:
1637 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1638 else:
1639 _, self.device = GetTypeAndDevice("/" + partition,
1640 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001641
Tao Baod8d14be2016-02-04 14:26:02 -08001642 @property
1643 def required_cache(self):
1644 return self._required_cache
1645
Tao Bao76def242017-11-21 09:25:31 -08001646 def WriteScript(self, script, output_zip, progress=None,
1647 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001648 if not self.src:
1649 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001650 script.Print("Patching %s image unconditionally..." % (self.partition,))
1651 else:
1652 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001653
Dan Albert8b72aef2015-03-23 19:13:21 -07001654 if progress:
1655 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001656 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001657
1658 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001659 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001660
Tao Bao9bc6bb22015-11-09 16:58:28 -08001661 def WriteStrictVerifyScript(self, script):
1662 """Verify all the blocks in the care_map, including clobbered blocks.
1663
1664 This differs from the WriteVerifyScript() function: a) it prints different
1665 error messages; b) it doesn't allow half-way updated images to pass the
1666 verification."""
1667
1668 partition = self.partition
1669 script.Print("Verifying %s..." % (partition,))
1670 ranges = self.tgt.care_map
1671 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001672 script.AppendExtra(
1673 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1674 'ui_print("\\"%s\\" has unexpected contents.");' % (
1675 self.device, ranges_str,
1676 self.tgt.TotalSha1(include_clobbered_blocks=True),
1677 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001678 script.AppendExtra("")
1679
Tao Baod522bdc2016-04-12 15:53:16 -07001680 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001681 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001682
1683 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001684 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001685 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001686
1687 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001688 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001689 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001690 ranges = self.touched_src_ranges
1691 expected_sha1 = self.touched_src_sha1
1692 else:
1693 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1694 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001695
1696 # No blocks to be checked, skipping.
1697 if not ranges:
1698 return
1699
Tao Bao5ece99d2015-05-12 11:42:31 -07001700 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001701 script.AppendExtra(
1702 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1703 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1704 '"%s.patch.dat")) then' % (
1705 self.device, ranges_str, expected_sha1,
1706 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001707 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001708 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001709
Tianjie Xufc3422a2015-12-15 11:53:59 -08001710 if self.version >= 4:
1711
1712 # Bug: 21124327
1713 # When generating incrementals for the system and vendor partitions in
1714 # version 4 or newer, explicitly check the first block (which contains
1715 # the superblock) of the partition to see if it's what we expect. If
1716 # this check fails, give an explicit log message about the partition
1717 # having been remounted R/W (the most likely explanation).
1718 if self.check_first_block:
1719 script.AppendExtra('check_first_block("%s");' % (self.device,))
1720
1721 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001722 if partition == "system":
1723 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1724 else:
1725 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001726 script.AppendExtra((
1727 'ifelse (block_image_recover("{device}", "{ranges}") && '
1728 'block_image_verify("{device}", '
1729 'package_extract_file("{partition}.transfer.list"), '
1730 '"{partition}.new.dat", "{partition}.patch.dat"), '
1731 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001732 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001733 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001734 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001735
Tao Baodd2a5892015-03-12 12:32:37 -07001736 # Abort the OTA update. Note that the incremental OTA cannot be applied
1737 # even if it may match the checksum of the target partition.
1738 # a) If version < 3, operations like move and erase will make changes
1739 # unconditionally and damage the partition.
1740 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001741 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001742 if partition == "system":
1743 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1744 else:
1745 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1746 script.AppendExtra((
1747 'abort("E%d: %s partition has unexpected contents");\n'
1748 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001749
Tao Bao5fcaaef2015-06-01 13:40:49 -07001750 def _WritePostInstallVerifyScript(self, script):
1751 partition = self.partition
1752 script.Print('Verifying the updated %s image...' % (partition,))
1753 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1754 ranges = self.tgt.care_map
1755 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001756 script.AppendExtra(
1757 'if range_sha1("%s", "%s") == "%s" then' % (
1758 self.device, ranges_str,
1759 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001760
1761 # Bug: 20881595
1762 # Verify that extended blocks are really zeroed out.
1763 if self.tgt.extended:
1764 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001765 script.AppendExtra(
1766 'if range_sha1("%s", "%s") == "%s" then' % (
1767 self.device, ranges_str,
1768 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001769 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001770 if partition == "system":
1771 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1772 else:
1773 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001774 script.AppendExtra(
1775 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001776 ' abort("E%d: %s partition has unexpected non-zero contents after '
1777 'OTA update");\n'
1778 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001779 else:
1780 script.Print('Verified the updated %s image.' % (partition,))
1781
Tianjie Xu209db462016-05-24 17:34:52 -07001782 if partition == "system":
1783 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1784 else:
1785 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1786
Tao Bao5fcaaef2015-06-01 13:40:49 -07001787 script.AppendExtra(
1788 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001789 ' abort("E%d: %s partition has unexpected contents after OTA '
1790 'update");\n'
1791 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001792
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001793 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001794 ZipWrite(output_zip,
1795 '{}.transfer.list'.format(self.path),
1796 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001797
Tao Bao76def242017-11-21 09:25:31 -08001798 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1799 # its size. Quailty 9 almost triples the compression time but doesn't
1800 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001801 # zip | brotli(quality 6) | brotli(quality 9)
1802 # compressed_size: 942M | 869M (~8% reduced) | 854M
1803 # compression_time: 75s | 265s | 719s
1804 # decompression_time: 15s | 25s | 25s
1805
1806 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001807 brotli_cmd = ['brotli', '--quality=6',
1808 '--output={}.new.dat.br'.format(self.path),
1809 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001810 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao80921982018-03-21 21:02:19 -07001811 p = Run(brotli_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1812 stdoutdata, _ = p.communicate()
1813 assert p.returncode == 0, \
1814 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1815 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001816
1817 new_data_name = '{}.new.dat.br'.format(self.partition)
1818 ZipWrite(output_zip,
1819 '{}.new.dat.br'.format(self.path),
1820 new_data_name,
1821 compress_type=zipfile.ZIP_STORED)
1822 else:
1823 new_data_name = '{}.new.dat'.format(self.partition)
1824 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1825
Dan Albert8e0178d2015-01-27 15:53:15 -08001826 ZipWrite(output_zip,
1827 '{}.patch.dat'.format(self.path),
1828 '{}.patch.dat'.format(self.partition),
1829 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001830
Tianjie Xu209db462016-05-24 17:34:52 -07001831 if self.partition == "system":
1832 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1833 else:
1834 code = ErrorCode.VENDOR_UPDATE_FAILURE
1835
Dan Albert8e0178d2015-01-27 15:53:15 -08001836 call = ('block_image_update("{device}", '
1837 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001838 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001839 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001840 device=self.device, partition=self.partition,
1841 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001842 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001843
Dan Albert8b72aef2015-03-23 19:13:21 -07001844 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001845 data = source.ReadRangeSet(ranges)
1846 ctx = sha1()
1847
1848 for p in data:
1849 ctx.update(p)
1850
1851 return ctx.hexdigest()
1852
Tao Baoe9b61912015-07-09 17:37:49 -07001853 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1854 """Return the hash value for all zero blocks."""
1855 zero_block = '\x00' * 4096
1856 ctx = sha1()
1857 for _ in range(num_blocks):
1858 ctx.update(zero_block)
1859
1860 return ctx.hexdigest()
1861
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001862
1863DataImage = blockimgdiff.DataImage
1864
Tao Bao76def242017-11-21 09:25:31 -08001865
Doug Zongker96a57e72010-09-26 14:57:41 -07001866# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001867PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001868 "ext4": "EMMC",
1869 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001870 "f2fs": "EMMC",
1871 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001872}
Doug Zongker96a57e72010-09-26 14:57:41 -07001873
Tao Bao76def242017-11-21 09:25:31 -08001874
Doug Zongker96a57e72010-09-26 14:57:41 -07001875def GetTypeAndDevice(mount_point, info):
1876 fstab = info["fstab"]
1877 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001878 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1879 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001880 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001881 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001882
1883
1884def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001885 """Parses and converts a PEM-encoded certificate into DER-encoded.
1886
1887 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1888
1889 Returns:
1890 The decoded certificate string.
1891 """
1892 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001893 save = False
1894 for line in data.split("\n"):
1895 if "--END CERTIFICATE--" in line:
1896 break
1897 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001898 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001899 if "--BEGIN CERTIFICATE--" in line:
1900 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001901 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001902 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001903
Tao Bao04e1f012018-02-04 12:13:35 -08001904
1905def ExtractPublicKey(cert):
1906 """Extracts the public key (PEM-encoded) from the given certificate file.
1907
1908 Args:
1909 cert: The certificate filename.
1910
1911 Returns:
1912 The public key string.
1913
1914 Raises:
1915 AssertionError: On non-zero return from 'openssl'.
1916 """
1917 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1918 # While openssl 1.1 writes the key into the given filename followed by '-out',
1919 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1920 # stdout instead.
1921 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1922 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1923 pubkey, stderrdata = proc.communicate()
1924 assert proc.returncode == 0, \
1925 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1926 return pubkey
1927
1928
Doug Zongker412c02f2014-02-13 10:58:24 -08001929def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1930 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001931 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001932
Tao Bao6d5d6232018-03-09 17:04:42 -08001933 Most of the space in the boot and recovery images is just the kernel, which is
1934 identical for the two, so the resulting patch should be efficient. Add it to
1935 the output zip, along with a shell script that is run from init.rc on first
1936 boot to actually do the patching and install the new recovery image.
1937
1938 Args:
1939 input_dir: The top-level input directory of the target-files.zip.
1940 output_sink: The callback function that writes the result.
1941 recovery_img: File object for the recovery image.
1942 boot_img: File objects for the boot image.
1943 info_dict: A dict returned by common.LoadInfoDict() on the input
1944 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001945 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001946 if info_dict is None:
1947 info_dict = OPTIONS.info_dict
1948
Tao Bao6d5d6232018-03-09 17:04:42 -08001949 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001950
Tao Baof2cffbd2015-07-22 12:33:18 -07001951 if full_recovery_image:
1952 output_sink("etc/recovery.img", recovery_img.data)
1953
1954 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08001955 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07001956 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08001957 # With system-root-image, boot and recovery images will have mismatching
1958 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
1959 # to handle such a case.
1960 if system_root_image:
1961 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07001962 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08001963 assert not os.path.exists(path)
1964 else:
1965 diff_program = ["imgdiff"]
1966 if os.path.exists(path):
1967 diff_program.append("-b")
1968 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07001969 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08001970 else:
1971 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07001972
1973 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1974 _, _, patch = d.ComputePatch()
1975 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001976
Dan Albertebb19aa2015-03-27 19:11:53 -07001977 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001978 # The following GetTypeAndDevice()s need to use the path in the target
1979 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001980 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1981 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1982 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001983 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001984
Tao Baof2cffbd2015-07-22 12:33:18 -07001985 if full_recovery_image:
1986 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07001987if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
1988 applypatch \\
1989 --flash /system/etc/recovery.img \\
1990 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
1991 log -t recovery "Installing new recovery image: succeeded" || \\
1992 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07001993else
1994 log -t recovery "Recovery image already installed"
1995fi
1996""" % {'type': recovery_type,
1997 'device': recovery_device,
1998 'sha1': recovery_img.sha1,
1999 'size': recovery_img.size}
2000 else:
2001 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002002if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2003 applypatch %(bonus_args)s \\
2004 --patch /system/recovery-from-boot.p \\
2005 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2006 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2007 log -t recovery "Installing new recovery image: succeeded" || \\
2008 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002009else
2010 log -t recovery "Recovery image already installed"
2011fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002012""" % {'boot_size': boot_img.size,
2013 'boot_sha1': boot_img.sha1,
2014 'recovery_size': recovery_img.size,
2015 'recovery_sha1': recovery_img.sha1,
2016 'boot_type': boot_type,
2017 'boot_device': boot_device,
2018 'recovery_type': recovery_type,
2019 'recovery_device': recovery_device,
2020 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002021
2022 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002023 # in the L release.
2024 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002025
Tao Bao89fbb0f2017-01-10 10:47:58 -08002026 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002027
2028 output_sink(sh_location, sh)