blob: 364d6ac0835d7f0127128ff8cebce932a5036a34 [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
Dan Albert8b72aef2015-03-23 19:13:21 -070047 self.search_path = platform_search_path.get(sys.platform, None)
48 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).
Jaekyun Seokb7735d82017-11-27 17:04:47 +090081AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product', 'dtbo')
Tao Bao9dd909e2017-11-14 11:27:32 -080082
83
Tianjie Xu209db462016-05-24 17:34:52 -070084class ErrorCode(object):
85 """Define error_codes for failures that happen during the actual
86 update package installation.
87
88 Error codes 0-999 are reserved for failures before the package
89 installation (i.e. low battery, package verification failure).
90 Detailed code in 'bootable/recovery/error_code.h' """
91
92 SYSTEM_VERIFICATION_FAILURE = 1000
93 SYSTEM_UPDATE_FAILURE = 1001
94 SYSTEM_UNEXPECTED_CONTENTS = 1002
95 SYSTEM_NONZERO_CONTENTS = 1003
96 SYSTEM_RECOVER_FAILURE = 1004
97 VENDOR_VERIFICATION_FAILURE = 2000
98 VENDOR_UPDATE_FAILURE = 2001
99 VENDOR_UNEXPECTED_CONTENTS = 2002
100 VENDOR_NONZERO_CONTENTS = 2003
101 VENDOR_RECOVER_FAILURE = 2004
102 OEM_PROP_MISMATCH = 3000
103 FINGERPRINT_MISMATCH = 3001
104 THUMBPRINT_MISMATCH = 3002
105 OLDER_BUILD = 3003
106 DEVICE_MISMATCH = 3004
107 BAD_PATCH_FILE = 3005
108 INSUFFICIENT_CACHE_SPACE = 3006
109 TUNE_PARTITION_FAILURE = 3007
110 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800111
Tao Bao80921982018-03-21 21:02:19 -0700112
Dan Albert8b72aef2015-03-23 19:13:21 -0700113class ExternalError(RuntimeError):
114 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700115
116
Tao Bao39451582017-05-04 11:10:47 -0700117def Run(args, verbose=None, **kwargs):
118 """Create and return a subprocess.Popen object.
119
120 Caller can specify if the command line should be printed. The global
121 OPTIONS.verbose will be used if not specified.
122 """
123 if verbose is None:
124 verbose = OPTIONS.verbose
125 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800126 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700127 return subprocess.Popen(args, **kwargs)
128
129
Tao Baoc765cca2018-01-31 17:32:40 -0800130def RoundUpTo4K(value):
131 rounded_up = value + 4095
132 return rounded_up - (rounded_up % 4096)
133
134
Ying Wang7e6d4e42010-12-13 16:25:36 -0800135def CloseInheritedPipes():
136 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
137 before doing other work."""
138 if platform.system() != "Darwin":
139 return
140 for d in range(3, 1025):
141 try:
142 stat = os.fstat(d)
143 if stat is not None:
144 pipebit = stat[0] & 0x1000
145 if pipebit != 0:
146 os.close(d)
147 except OSError:
148 pass
149
150
Tao Bao2c15d9e2015-07-09 11:51:16 -0700151def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700152 """Read and parse the META/misc_info.txt key/value pairs from the
153 input target files and return a dict."""
154
Doug Zongkerc9253822014-02-04 12:17:58 -0800155 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700156 if isinstance(input_file, zipfile.ZipFile):
157 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800158 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700159 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800160 try:
161 with open(path) as f:
162 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700163 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800164 if e.errno == errno.ENOENT:
165 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800166
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700167 try:
Michael Runge6e836112014-04-15 17:40:21 -0700168 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700169 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800170 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700171
Tao Bao6cd54732017-02-27 15:12:05 -0800172 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800173 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800174
Tao Bao84e75682015-07-19 02:38:53 -0700175 # A few properties are stored as links to the files in the out/ directory.
176 # It works fine with the build system. However, they are no longer available
177 # when (re)generating from target_files zip. If input_dir is not None, we
178 # are doing repacking. Redirect those properties to the actual files in the
179 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700180 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400181 # We carry a copy of file_contexts.bin under META/. If not available,
182 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700183 # to build images than the one running on device, such as when enabling
184 # system_root_image. In that case, we must have the one for image
185 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700186 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
187 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700188 if d.get("system_root_image") == "true":
189 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700190 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700191 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700192 if not os.path.exists(fc_config):
193 fc_config = None
194
195 if fc_config:
196 d["selinux_fc"] = fc_config
197
Tao Bao84e75682015-07-19 02:38:53 -0700198 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
199 if d.get("system_root_image") == "true":
200 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
201 d["ramdisk_fs_config"] = os.path.join(
202 input_dir, "META", "root_filesystem_config.txt")
203
Tao Baof54216f2016-03-29 15:12:37 -0700204 # Redirect {system,vendor}_base_fs_file.
205 if "system_base_fs_file" in d:
206 basename = os.path.basename(d["system_base_fs_file"])
207 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700208 if os.path.exists(system_base_fs_file):
209 d["system_base_fs_file"] = system_base_fs_file
210 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800211 print("Warning: failed to find system base fs file: %s" % (
212 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700213 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700214
215 if "vendor_base_fs_file" in d:
216 basename = os.path.basename(d["vendor_base_fs_file"])
217 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700218 if os.path.exists(vendor_base_fs_file):
219 d["vendor_base_fs_file"] = vendor_base_fs_file
220 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800221 print("Warning: failed to find vendor base fs file: %s" % (
222 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700223 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700224
Doug Zongker37974732010-09-16 17:44:38 -0700225 def makeint(key):
226 if key in d:
227 d[key] = int(d[key], 0)
228
229 makeint("recovery_api_version")
230 makeint("blocksize")
231 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700232 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700233 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700234 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700235 makeint("recovery_size")
236 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800237 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700238
Tianjie Xucfa86222016-03-07 16:31:19 -0800239 system_root_image = d.get("system_root_image", None) == "true"
240 if d.get("no_recovery", None) != "true":
241 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800242 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800243 recovery_fstab_path, system_root_image)
244 elif d.get("recovery_as_boot", None) == "true":
245 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
246 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
247 recovery_fstab_path, system_root_image)
248 else:
249 d["fstab"] = None
250
Tao Baobcd1d162017-08-26 13:10:26 -0700251 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
252 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Tao Bao12d87fc2018-01-31 12:18:52 -0800253
254 # Set up the salt (based on fingerprint or thumbprint) that will be used when
255 # adding AVB footer.
256 if d.get("avb_enable") == "true":
257 fp = None
258 if "build.prop" in d:
259 build_prop = d["build.prop"]
260 if "ro.build.fingerprint" in build_prop:
261 fp = build_prop["ro.build.fingerprint"]
262 elif "ro.build.thumbprint" in build_prop:
263 fp = build_prop["ro.build.thumbprint"]
264 if fp:
265 d["avb_salt"] = sha256(fp).hexdigest()
266
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700267 return d
268
Tao Baod1de6f32017-03-01 16:38:48 -0800269
Tao Baobcd1d162017-08-26 13:10:26 -0700270def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700271 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700272 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700273 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700274 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700275 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700276 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700277
Tao Baod1de6f32017-03-01 16:38:48 -0800278
Michael Runge6e836112014-04-15 17:40:21 -0700279def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700280 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700281 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700282 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700283 if not line or line.startswith("#"):
284 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700285 if "=" in line:
286 name, value = line.split("=", 1)
287 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700288 return d
289
Tao Baod1de6f32017-03-01 16:38:48 -0800290
Tianjie Xucfa86222016-03-07 16:31:19 -0800291def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
292 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700293 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800294 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700295 self.mount_point = mount_point
296 self.fs_type = fs_type
297 self.device = device
298 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700299 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700300
301 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800302 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700303 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800304 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700305 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700306
Tao Baod1de6f32017-03-01 16:38:48 -0800307 assert fstab_version == 2
308
309 d = {}
310 for line in data.split("\n"):
311 line = line.strip()
312 if not line or line.startswith("#"):
313 continue
314
315 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
316 pieces = line.split()
317 if len(pieces) != 5:
318 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
319
320 # Ignore entries that are managed by vold.
321 options = pieces[4]
322 if "voldmanaged=" in options:
323 continue
324
325 # It's a good line, parse it.
326 length = 0
327 options = options.split(",")
328 for i in options:
329 if i.startswith("length="):
330 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800331 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800332 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700333 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800334
Tao Baod1de6f32017-03-01 16:38:48 -0800335 mount_flags = pieces[3]
336 # Honor the SELinux context if present.
337 context = None
338 for i in mount_flags.split(","):
339 if i.startswith("context="):
340 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800341
Tao Baod1de6f32017-03-01 16:38:48 -0800342 mount_point = pieces[1]
343 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
344 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800345
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700346 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700347 # system. Other areas assume system is always at "/system" so point /system
348 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700349 if system_root_image:
350 assert not d.has_key("/system") and d.has_key("/")
351 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700352 return d
353
354
Doug Zongker37974732010-09-16 17:44:38 -0700355def DumpInfoDict(d):
356 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800357 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700358
Dan Albert8b72aef2015-03-23 19:13:21 -0700359
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800360def AppendAVBSigningArgs(cmd, partition):
361 """Append signing arguments for avbtool."""
362 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
363 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
364 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
365 if key_path and algorithm:
366 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700367 avb_salt = OPTIONS.info_dict.get("avb_salt")
368 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
369 if avb_salt and partition != "vbmeta":
370 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800371
372
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700373def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800374 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700375 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700376
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700377 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800378 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
379 we are building a two-step special image (i.e. building a recovery image to
380 be loaded into /boot in two-step OTAs).
381
382 Return the image data, or None if sourcedir does not appear to contains files
383 for building the requested image.
384 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700385
386 def make_ramdisk():
387 ramdisk_img = tempfile.NamedTemporaryFile()
388
389 if os.access(fs_config_file, os.F_OK):
390 cmd = ["mkbootfs", "-f", fs_config_file,
391 os.path.join(sourcedir, "RAMDISK")]
392 else:
393 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
394 p1 = Run(cmd, stdout=subprocess.PIPE)
395 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
396
397 p2.wait()
398 p1.wait()
399 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
400 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
401
402 return ramdisk_img
403
404 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
405 return None
406
407 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700408 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700409
Doug Zongkerd5131602012-08-02 14:46:42 -0700410 if info_dict is None:
411 info_dict = OPTIONS.info_dict
412
Doug Zongkereef39442009-04-02 12:14:19 -0700413 img = tempfile.NamedTemporaryFile()
414
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700415 if has_ramdisk:
416 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700417
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800418 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
419 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
420
421 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700422
Benoit Fradina45a8682014-07-14 21:00:43 +0200423 fn = os.path.join(sourcedir, "second")
424 if os.access(fn, os.F_OK):
425 cmd.append("--second")
426 cmd.append(fn)
427
Doug Zongker171f1cd2009-06-15 22:36:37 -0700428 fn = os.path.join(sourcedir, "cmdline")
429 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700430 cmd.append("--cmdline")
431 cmd.append(open(fn).read().rstrip("\n"))
432
433 fn = os.path.join(sourcedir, "base")
434 if os.access(fn, os.F_OK):
435 cmd.append("--base")
436 cmd.append(open(fn).read().rstrip("\n"))
437
Ying Wang4de6b5b2010-08-25 14:29:34 -0700438 fn = os.path.join(sourcedir, "pagesize")
439 if os.access(fn, os.F_OK):
440 cmd.append("--pagesize")
441 cmd.append(open(fn).read().rstrip("\n"))
442
Doug Zongkerd5131602012-08-02 14:46:42 -0700443 args = info_dict.get("mkbootimg_args", None)
444 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700445 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700446
Sami Tolvanen3303d902016-03-15 16:49:30 +0000447 args = info_dict.get("mkbootimg_version_args", None)
448 if args and args.strip():
449 cmd.extend(shlex.split(args))
450
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700451 if has_ramdisk:
452 cmd.extend(["--ramdisk", ramdisk_img.name])
453
Tao Baod95e9fd2015-03-29 23:07:41 -0700454 img_unsigned = None
455 if info_dict.get("vboot", None):
456 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700457 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700458 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700459 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700460
Tao Baobf70c3182017-07-11 17:27:55 -0700461 # "boot" or "recovery", without extension.
462 partition_name = os.path.basename(sourcedir).lower()
463
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700464 if (partition_name == "recovery" and
465 info_dict.get("include_recovery_dtbo") == "true"):
466 fn = os.path.join(sourcedir, "recovery_dtbo")
467 cmd.extend(["--recovery_dtbo", fn])
468
Doug Zongker38a649f2009-06-17 09:07:09 -0700469 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700470 p.communicate()
Tao Baobf70c3182017-07-11 17:27:55 -0700471 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700472
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100473 if (info_dict.get("boot_signer", None) == "true" and
474 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800475 # Hard-code the path as "/boot" for two-step special recovery image (which
476 # will be loaded into /boot during the two-step OTA).
477 if two_step_image:
478 path = "/boot"
479 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700480 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700481 cmd = [OPTIONS.boot_signer_path]
482 cmd.extend(OPTIONS.boot_signer_args)
483 cmd.extend([path, img.name,
484 info_dict["verity_key"] + ".pk8",
485 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700486 p = Run(cmd, stdout=subprocess.PIPE)
487 p.communicate()
488 assert p.returncode == 0, "boot_signer of %s image failed" % path
489
Tao Baod95e9fd2015-03-29 23:07:41 -0700490 # Sign the image if vboot is non-empty.
491 elif info_dict.get("vboot", None):
Tao Baobf70c3182017-07-11 17:27:55 -0700492 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700493 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800494 # We have switched from the prebuilt futility binary to using the tool
495 # (futility-host) built from the source. Override the setting in the old
496 # TF.zip.
497 futility = info_dict["futility"]
498 if futility.startswith("prebuilts/"):
499 futility = "futility-host"
500 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700501 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700502 info_dict["vboot_key"] + ".vbprivk",
503 info_dict["vboot_subkey"] + ".vbprivk",
504 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700505 img.name]
506 p = Run(cmd, stdout=subprocess.PIPE)
507 p.communicate()
508 assert p.returncode == 0, "vboot_signer of %s image failed" % path
509
Tao Baof3282b42015-04-01 11:21:55 -0700510 # Clean up the temp files.
511 img_unsigned.close()
512 img_keyblock.close()
513
David Zeuthen8fecb282017-12-01 16:24:01 -0500514 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800515 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700516 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500517 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400518 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700519 "--partition_size", str(part_size), "--partition_name",
520 partition_name]
521 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500522 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400523 if args and args.strip():
524 cmd.extend(shlex.split(args))
525 p = Run(cmd, stdout=subprocess.PIPE)
526 p.communicate()
527 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c3182017-07-11 17:27:55 -0700528 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500529
530 img.seek(os.SEEK_SET, 0)
531 data = img.read()
532
533 if has_ramdisk:
534 ramdisk_img.close()
535 img.close()
536
537 return data
538
539
Doug Zongkerd5131602012-08-02 14:46:42 -0700540def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800541 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700542 """Return a File object with the desired bootable image.
543
544 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
545 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
546 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700547
Doug Zongker55d93282011-01-25 17:03:34 -0800548 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
549 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800550 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800551 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700552
553 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
554 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800555 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700556 return File.FromLocalFile(name, prebuilt_path)
557
Tao Bao89fbb0f2017-01-10 10:47:58 -0800558 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700559
560 if info_dict is None:
561 info_dict = OPTIONS.info_dict
562
563 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800564 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
565 # for recovery.
566 has_ramdisk = (info_dict.get("system_root_image") != "true" or
567 prebuilt_name != "boot.img" or
568 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700569
Doug Zongker6f1d0312014-08-22 08:07:12 -0700570 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400571 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
572 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800573 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700574 if data:
575 return File(name, data)
576 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800577
Doug Zongkereef39442009-04-02 12:14:19 -0700578
Narayan Kamatha07bf042017-08-14 14:49:21 +0100579def Gunzip(in_filename, out_filename):
580 """Gunzip the given gzip compressed file to a given output file.
581 """
582 with gzip.open(in_filename, "rb") as in_file, open(out_filename, "wb") as out_file:
583 shutil.copyfileobj(in_file, out_file)
584
585
Doug Zongker75f17362009-12-08 13:46:44 -0800586def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800587 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800588
Tao Bao1c830bf2017-12-25 10:43:47 -0800589 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
590 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800591
Tao Bao1c830bf2017-12-25 10:43:47 -0800592 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800593 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800594 """
Doug Zongkereef39442009-04-02 12:14:19 -0700595
Doug Zongker55d93282011-01-25 17:03:34 -0800596 def unzip_to_dir(filename, dirname):
597 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
598 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800599 cmd.extend(pattern)
Tao Bao80921982018-03-21 21:02:19 -0700600 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
601 stdoutdata, _ = p.communicate()
Doug Zongker55d93282011-01-25 17:03:34 -0800602 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700603 raise ExternalError(
604 "Failed to unzip input target-files \"{}\":\n{}".format(
605 filename, stdoutdata))
Doug Zongker55d93282011-01-25 17:03:34 -0800606
Tao Bao1c830bf2017-12-25 10:43:47 -0800607 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800608 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
609 if m:
610 unzip_to_dir(m.group(1), tmp)
611 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
612 filename = m.group(1)
613 else:
614 unzip_to_dir(filename, tmp)
615
Tao Baodba59ee2018-01-09 13:21:02 -0800616 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700617
618
Tao Baoe709b092018-02-07 12:40:00 -0800619def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -0800620 """Returns a SparseImage object suitable for passing to BlockImageDiff.
621
622 This function loads the specified sparse image from the given path, and
623 performs additional processing for OTA purpose. For example, it always adds
624 block 0 to clobbered blocks list. It also detects files that cannot be
625 reconstructed from the block list, for whom we should avoid applying imgdiff.
626
627 Args:
628 which: The partition name, which must be "system" or "vendor".
629 tmpdir: The directory that contains the prebuilt image and block map file.
630 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800631 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -0800632
633 Returns:
634 A SparseImage object, with file_map info loaded.
635 """
636 assert which in ("system", "vendor")
637
638 path = os.path.join(tmpdir, "IMAGES", which + ".img")
639 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
640
641 # The image and map files must have been created prior to calling
642 # ota_from_target_files.py (since LMP).
643 assert os.path.exists(path) and os.path.exists(mappath)
644
645 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
646 # it to clobbered_blocks so that it will be written to the target
647 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
648 clobbered_blocks = "0"
649
Tao Baoe709b092018-02-07 12:40:00 -0800650 image = sparse_img.SparseImage(path, mappath, clobbered_blocks,
651 allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -0800652
653 # block.map may contain less blocks, because mke2fs may skip allocating blocks
654 # if they contain all zeros. We can't reconstruct such a file from its block
655 # list. Tag such entries accordingly. (Bug: 65213616)
656 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800657 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700658 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800659 continue
660
Tao Baod3554e62018-07-10 15:31:22 -0700661 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that when
662 # using system_root_image, the filename listed in system.map may contain an
663 # additional leading slash (i.e. "//system/framework/am.jar"). Using lstrip
664 # to get consistent results.
665 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
666
667 # Special handling another case with system_root_image, where files not
668 # under /system (e.g. "/sbin/charger") are packed under ROOT/ in a
669 # target_files.zip.
670 if which == 'system' and not arcname.startswith('SYSTEM'):
671 arcname = 'ROOT/' + arcname
672
673 assert arcname in input_zip.namelist(), \
674 "Failed to find the ZIP entry for {}".format(entry)
675
Tao Baoc765cca2018-01-31 17:32:40 -0800676 info = input_zip.getinfo(arcname)
677 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800678
679 # If a RangeSet has been tagged as using shared blocks while loading the
680 # image, its block list must be already incomplete due to that reason. Don't
681 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
682 if ranges.extra.get('uses_shared_blocks'):
683 continue
684
Tao Baoc765cca2018-01-31 17:32:40 -0800685 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
686 ranges.extra['incomplete'] = True
687
688 return image
689
690
Doug Zongkereef39442009-04-02 12:14:19 -0700691def GetKeyPasswords(keylist):
692 """Given a list of keys, prompt the user to enter passwords for
693 those which require them. Return a {key: password} dict. password
694 will be None if the key has no password."""
695
Doug Zongker8ce7c252009-05-22 13:34:54 -0700696 no_passwords = []
697 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700698 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700699 devnull = open("/dev/null", "w+b")
700 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800701 # We don't need a password for things that aren't really keys.
702 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700703 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700704 continue
705
T.R. Fullhart37e10522013-03-18 10:31:26 -0700706 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700707 "-inform", "DER", "-nocrypt"],
708 stdin=devnull.fileno(),
709 stdout=devnull.fileno(),
710 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700711 p.communicate()
712 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700713 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700714 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700715 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700716 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
717 "-inform", "DER", "-passin", "pass:"],
718 stdin=devnull.fileno(),
719 stdout=devnull.fileno(),
720 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700721 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700722 if p.returncode == 0:
723 # Encrypted key with empty string as password.
724 key_passwords[k] = ''
725 elif stderr.startswith('Error decrypting key'):
726 # Definitely encrypted key.
727 # It would have said "Error reading key" if it didn't parse correctly.
728 need_passwords.append(k)
729 else:
730 # Potentially, a type of key that openssl doesn't understand.
731 # We'll let the routines in signapk.jar handle it.
732 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700733 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700734
T.R. Fullhart37e10522013-03-18 10:31:26 -0700735 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700736 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700737 return key_passwords
738
739
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800740def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700741 """Gets the minSdkVersion declared in the APK.
742
743 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
744 This can be both a decimal number (API Level) or a codename.
745
746 Args:
747 apk_name: The APK filename.
748
749 Returns:
750 The parsed SDK version string.
751
752 Raises:
753 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800754 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700755 proc = Run(
756 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
757 stderr=subprocess.PIPE)
758 stdoutdata, stderrdata = proc.communicate()
759 if proc.returncode != 0:
760 raise ExternalError(
761 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
762 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800763
Tao Baof47bf0f2018-03-21 23:28:51 -0700764 for line in stdoutdata.split("\n"):
765 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800766 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
767 if m:
768 return m.group(1)
769 raise ExternalError("No minSdkVersion returned by aapt")
770
771
772def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700773 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800774
Tao Baof47bf0f2018-03-21 23:28:51 -0700775 If minSdkVersion is set to a codename, it is translated to a number using the
776 provided map.
777
778 Args:
779 apk_name: The APK filename.
780
781 Returns:
782 The parsed SDK version number.
783
784 Raises:
785 ExternalError: On failing to get the min SDK version number.
786 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800787 version = GetMinSdkVersion(apk_name)
788 try:
789 return int(version)
790 except ValueError:
791 # Not a decimal number. Codename?
792 if version in codename_to_api_level_map:
793 return codename_to_api_level_map[version]
794 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700795 raise ExternalError(
796 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
797 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800798
799
800def SignFile(input_name, output_name, key, password, min_api_level=None,
801 codename_to_api_level_map=dict(),
802 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700803 """Sign the input_name zip/jar/apk, producing output_name. Use the
804 given key and password (the latter may be None if the key does not
805 have a password.
806
Doug Zongker951495f2009-08-14 12:44:19 -0700807 If whole_file is true, use the "-w" option to SignApk to embed a
808 signature that covers the whole file in the archive comment of the
809 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800810
811 min_api_level is the API Level (int) of the oldest platform this file may end
812 up on. If not specified for an APK, the API Level is obtained by interpreting
813 the minSdkVersion attribute of the APK's AndroidManifest.xml.
814
815 codename_to_api_level_map is needed to translate the codename which may be
816 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700817 """
Doug Zongker951495f2009-08-14 12:44:19 -0700818
Alex Klyubin9667b182015-12-10 13:38:50 -0800819 java_library_path = os.path.join(
820 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
821
Tao Baoe95540e2016-11-08 12:08:53 -0800822 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
823 ["-Djava.library.path=" + java_library_path,
824 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
825 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700826 if whole_file:
827 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800828
829 min_sdk_version = min_api_level
830 if min_sdk_version is None:
831 if not whole_file:
832 min_sdk_version = GetMinSdkVersionInt(
833 input_name, codename_to_api_level_map)
834 if min_sdk_version is not None:
835 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
836
T.R. Fullhart37e10522013-03-18 10:31:26 -0700837 cmd.extend([key + OPTIONS.public_key_suffix,
838 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800839 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700840
Tao Bao80921982018-03-21 21:02:19 -0700841 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
842 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700843 if password is not None:
844 password += "\n"
Tao Bao80921982018-03-21 21:02:19 -0700845 stdoutdata, _ = p.communicate(password)
Doug Zongkereef39442009-04-02 12:14:19 -0700846 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700847 raise ExternalError(
848 "Failed to run signapk.jar: return code {}:\n{}".format(
849 p.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700850
Doug Zongkereef39442009-04-02 12:14:19 -0700851
Doug Zongker37974732010-09-16 17:44:38 -0700852def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800853 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700854
Tao Bao9dd909e2017-11-14 11:27:32 -0800855 For non-AVB images, raise exception if the data is too big. Print a warning
856 if the data is nearing the maximum size.
857
858 For AVB images, the actual image size should be identical to the limit.
859
860 Args:
861 data: A string that contains all the data for the partition.
862 target: The partition name. The ".img" suffix is optional.
863 info_dict: The dict to be looked up for relevant info.
864 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700865 if target.endswith(".img"):
866 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700867 mount_point = "/" + target
868
Ying Wangf8824af2014-06-03 14:07:27 -0700869 fs_type = None
870 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700871 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700872 if mount_point == "/userdata":
873 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700874 p = info_dict["fstab"][mount_point]
875 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800876 device = p.device
877 if "/" in device:
878 device = device[device.rfind("/")+1:]
879 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700880 if not fs_type or not limit:
881 return
Doug Zongkereef39442009-04-02 12:14:19 -0700882
Andrew Boie0f9aec82012-02-14 09:32:52 -0800883 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800884 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
885 # path.
886 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
887 if size != limit:
888 raise ExternalError(
889 "Mismatching image size for %s: expected %d actual %d" % (
890 target, limit, size))
891 else:
892 pct = float(size) * 100.0 / limit
893 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
894 if pct >= 99.0:
895 raise ExternalError(msg)
896 elif pct >= 95.0:
897 print("\n WARNING: %s\n" % (msg,))
898 elif OPTIONS.verbose:
899 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700900
901
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800902def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800903 """Parses the APK certs info from a given target-files zip.
904
905 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
906 tuple with the following elements: (1) a dictionary that maps packages to
907 certs (based on the "certificate" and "private_key" attributes in the file;
908 (2) a string representing the extension of compressed APKs in the target files
909 (e.g ".gz", ".bro").
910
911 Args:
912 tf_zip: The input target_files ZipFile (already open).
913
914 Returns:
915 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
916 the extension string of compressed APKs (e.g. ".gz"), or None if there's
917 no compressed APKs.
918 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800919 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100920 compressed_extension = None
921
Tao Bao0f990332017-09-08 19:02:54 -0700922 # META/apkcerts.txt contains the info for _all_ the packages known at build
923 # time. Filter out the ones that are not installed.
924 installed_files = set()
925 for name in tf_zip.namelist():
926 basename = os.path.basename(name)
927 if basename:
928 installed_files.add(basename)
929
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800930 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
931 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700932 if not line:
933 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800934 m = re.match(
935 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
936 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
937 line)
938 if not m:
939 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100940
Tao Bao818ddf52018-01-05 11:17:34 -0800941 matches = m.groupdict()
942 cert = matches["CERT"]
943 privkey = matches["PRIVKEY"]
944 name = matches["NAME"]
945 this_compressed_extension = matches["COMPRESSED"]
946
947 public_key_suffix_len = len(OPTIONS.public_key_suffix)
948 private_key_suffix_len = len(OPTIONS.private_key_suffix)
949 if cert in SPECIAL_CERT_STRINGS and not privkey:
950 certmap[name] = cert
951 elif (cert.endswith(OPTIONS.public_key_suffix) and
952 privkey.endswith(OPTIONS.private_key_suffix) and
953 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
954 certmap[name] = cert[:-public_key_suffix_len]
955 else:
956 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
957
958 if not this_compressed_extension:
959 continue
960
961 # Only count the installed files.
962 filename = name + '.' + this_compressed_extension
963 if filename not in installed_files:
964 continue
965
966 # Make sure that all the values in the compression map have the same
967 # extension. We don't support multiple compression methods in the same
968 # system image.
969 if compressed_extension:
970 if this_compressed_extension != compressed_extension:
971 raise ValueError(
972 "Multiple compressed extensions: {} vs {}".format(
973 compressed_extension, this_compressed_extension))
974 else:
975 compressed_extension = this_compressed_extension
976
977 return (certmap,
978 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800979
980
Doug Zongkereef39442009-04-02 12:14:19 -0700981COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -0700982Global options
983
984 -p (--path) <dir>
985 Prepend <dir>/bin to the list of places to search for binaries run by this
986 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700987
Doug Zongker05d3dea2009-06-22 11:32:31 -0700988 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -0700989 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -0700990
Tao Bao30df8b42018-04-23 15:32:53 -0700991 -x (--extra) <key=value>
992 Add a key/value pair to the 'extras' dict, which device-specific extension
993 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -0800994
Doug Zongkereef39442009-04-02 12:14:19 -0700995 -v (--verbose)
996 Show command lines being executed.
997
998 -h (--help)
999 Display this usage message and exit.
1000"""
1001
1002def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001003 print(docstring.rstrip("\n"))
1004 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001005
1006
1007def ParseOptions(argv,
1008 docstring,
1009 extra_opts="", extra_long_opts=(),
1010 extra_option_handler=None):
1011 """Parse the options in argv and return any arguments that aren't
1012 flags. docstring is the calling module's docstring, to be displayed
1013 for errors and -h. extra_opts and extra_long_opts are for flags
1014 defined by the caller, which are processed by passing them to
1015 extra_option_handler."""
1016
1017 try:
1018 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001019 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001020 ["help", "verbose", "path=", "signapk_path=",
1021 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001022 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001023 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1024 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001025 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001026 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001027 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001028 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001029 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001030 sys.exit(2)
1031
Doug Zongkereef39442009-04-02 12:14:19 -07001032 for o, a in opts:
1033 if o in ("-h", "--help"):
1034 Usage(docstring)
1035 sys.exit()
1036 elif o in ("-v", "--verbose"):
1037 OPTIONS.verbose = True
1038 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001039 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001040 elif o in ("--signapk_path",):
1041 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001042 elif o in ("--signapk_shared_library_path",):
1043 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001044 elif o in ("--extra_signapk_args",):
1045 OPTIONS.extra_signapk_args = shlex.split(a)
1046 elif o in ("--java_path",):
1047 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001048 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001049 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001050 elif o in ("--public_key_suffix",):
1051 OPTIONS.public_key_suffix = a
1052 elif o in ("--private_key_suffix",):
1053 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001054 elif o in ("--boot_signer_path",):
1055 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001056 elif o in ("--boot_signer_args",):
1057 OPTIONS.boot_signer_args = shlex.split(a)
1058 elif o in ("--verity_signer_path",):
1059 OPTIONS.verity_signer_path = a
1060 elif o in ("--verity_signer_args",):
1061 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001062 elif o in ("-s", "--device_specific"):
1063 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001064 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001065 key, value = a.split("=", 1)
1066 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001067 else:
1068 if extra_option_handler is None or not extra_option_handler(o, a):
1069 assert False, "unknown option \"%s\"" % (o,)
1070
Doug Zongker85448772014-09-09 14:59:20 -07001071 if OPTIONS.search_path:
1072 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1073 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001074
1075 return args
1076
1077
Tao Bao4c851b12016-09-19 13:54:38 -07001078def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001079 """Make a temp file and add it to the list of things to be deleted
1080 when Cleanup() is called. Return the filename."""
1081 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1082 os.close(fd)
1083 OPTIONS.tempfiles.append(fn)
1084 return fn
1085
1086
Tao Bao1c830bf2017-12-25 10:43:47 -08001087def MakeTempDir(prefix='tmp', suffix=''):
1088 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1089
1090 Returns:
1091 The absolute pathname of the new directory.
1092 """
1093 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1094 OPTIONS.tempfiles.append(dir_name)
1095 return dir_name
1096
1097
Doug Zongkereef39442009-04-02 12:14:19 -07001098def Cleanup():
1099 for i in OPTIONS.tempfiles:
1100 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001101 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001102 else:
1103 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001104 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001105
1106
1107class PasswordManager(object):
1108 def __init__(self):
1109 self.editor = os.getenv("EDITOR", None)
1110 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
1111
1112 def GetPasswords(self, items):
1113 """Get passwords corresponding to each string in 'items',
1114 returning a dict. (The dict may have keys in addition to the
1115 values in 'items'.)
1116
1117 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1118 user edit that file to add more needed passwords. If no editor is
1119 available, or $ANDROID_PW_FILE isn't define, prompts the user
1120 interactively in the ordinary way.
1121 """
1122
1123 current = self.ReadFile()
1124
1125 first = True
1126 while True:
1127 missing = []
1128 for i in items:
1129 if i not in current or not current[i]:
1130 missing.append(i)
1131 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001132 if not missing:
1133 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001134
1135 for i in missing:
1136 current[i] = ""
1137
1138 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001139 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001140 answer = raw_input("try to edit again? [y]> ").strip()
1141 if answer and answer[0] not in 'yY':
1142 raise RuntimeError("key passwords unavailable")
1143 first = False
1144
1145 current = self.UpdateAndReadFile(current)
1146
Dan Albert8b72aef2015-03-23 19:13:21 -07001147 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001148 """Prompt the user to enter a value (password) for each key in
1149 'current' whose value is fales. Returns a new dict with all the
1150 values.
1151 """
1152 result = {}
1153 for k, v in sorted(current.iteritems()):
1154 if v:
1155 result[k] = v
1156 else:
1157 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001158 result[k] = getpass.getpass(
1159 "Enter password for %s key> " % k).strip()
1160 if result[k]:
1161 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001162 return result
1163
1164 def UpdateAndReadFile(self, current):
1165 if not self.editor or not self.pwfile:
1166 return self.PromptResult(current)
1167
1168 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001169 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001170 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1171 f.write("# (Additional spaces are harmless.)\n\n")
1172
1173 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001174 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1175 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001176 f.write("[[[ %s ]]] %s\n" % (v, k))
1177 if not v and first_line is None:
1178 # position cursor on first line with no password.
1179 first_line = i + 4
1180 f.close()
1181
1182 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1183 _, _ = p.communicate()
1184
1185 return self.ReadFile()
1186
1187 def ReadFile(self):
1188 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001189 if self.pwfile is None:
1190 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001191 try:
1192 f = open(self.pwfile, "r")
1193 for line in f:
1194 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001195 if not line or line[0] == '#':
1196 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001197 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1198 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001199 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001200 else:
1201 result[m.group(2)] = m.group(1)
1202 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001203 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001204 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001205 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001206 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001207
1208
Dan Albert8e0178d2015-01-27 15:53:15 -08001209def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1210 compress_type=None):
1211 import datetime
1212
1213 # http://b/18015246
1214 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1215 # for files larger than 2GiB. We can work around this by adjusting their
1216 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1217 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1218 # it isn't clear to me exactly what circumstances cause this).
1219 # `zipfile.write()` must be used directly to work around this.
1220 #
1221 # This mess can be avoided if we port to python3.
1222 saved_zip64_limit = zipfile.ZIP64_LIMIT
1223 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1224
1225 if compress_type is None:
1226 compress_type = zip_file.compression
1227 if arcname is None:
1228 arcname = filename
1229
1230 saved_stat = os.stat(filename)
1231
1232 try:
1233 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1234 # file to be zipped and reset it when we're done.
1235 os.chmod(filename, perms)
1236
1237 # Use a fixed timestamp so the output is repeatable.
1238 epoch = datetime.datetime.fromtimestamp(0)
1239 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1240 os.utime(filename, (timestamp, timestamp))
1241
1242 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1243 finally:
1244 os.chmod(filename, saved_stat.st_mode)
1245 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1246 zipfile.ZIP64_LIMIT = saved_zip64_limit
1247
1248
Tao Bao58c1b962015-05-20 09:32:18 -07001249def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001250 compress_type=None):
1251 """Wrap zipfile.writestr() function to work around the zip64 limit.
1252
1253 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1254 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1255 when calling crc32(bytes).
1256
1257 But it still works fine to write a shorter string into a large zip file.
1258 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1259 when we know the string won't be too long.
1260 """
1261
1262 saved_zip64_limit = zipfile.ZIP64_LIMIT
1263 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1264
1265 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1266 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001267 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001268 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001269 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001270 else:
Tao Baof3282b42015-04-01 11:21:55 -07001271 zinfo = zinfo_or_arcname
1272
1273 # If compress_type is given, it overrides the value in zinfo.
1274 if compress_type is not None:
1275 zinfo.compress_type = compress_type
1276
Tao Bao58c1b962015-05-20 09:32:18 -07001277 # If perms is given, it has a priority.
1278 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001279 # If perms doesn't set the file type, mark it as a regular file.
1280 if perms & 0o770000 == 0:
1281 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001282 zinfo.external_attr = perms << 16
1283
Tao Baof3282b42015-04-01 11:21:55 -07001284 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001285 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1286
Dan Albert8b72aef2015-03-23 19:13:21 -07001287 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001288 zipfile.ZIP64_LIMIT = saved_zip64_limit
1289
1290
Tao Bao89d7ab22017-12-14 17:05:33 -08001291def ZipDelete(zip_filename, entries):
1292 """Deletes entries from a ZIP file.
1293
1294 Since deleting entries from a ZIP file is not supported, it shells out to
1295 'zip -d'.
1296
1297 Args:
1298 zip_filename: The name of the ZIP file.
1299 entries: The name of the entry, or the list of names to be deleted.
1300
1301 Raises:
1302 AssertionError: In case of non-zero return from 'zip'.
1303 """
1304 if isinstance(entries, basestring):
1305 entries = [entries]
1306 cmd = ["zip", "-d", zip_filename] + entries
1307 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1308 stdoutdata, _ = proc.communicate()
1309 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1310 stdoutdata)
1311
1312
Tao Baof3282b42015-04-01 11:21:55 -07001313def ZipClose(zip_file):
1314 # http://b/18015246
1315 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1316 # central directory.
1317 saved_zip64_limit = zipfile.ZIP64_LIMIT
1318 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1319
1320 zip_file.close()
1321
1322 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001323
1324
1325class DeviceSpecificParams(object):
1326 module = None
1327 def __init__(self, **kwargs):
1328 """Keyword arguments to the constructor become attributes of this
1329 object, which is passed to all functions in the device-specific
1330 module."""
1331 for k, v in kwargs.iteritems():
1332 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001333 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001334
1335 if self.module is None:
1336 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001337 if not path:
1338 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001339 try:
1340 if os.path.isdir(path):
1341 info = imp.find_module("releasetools", [path])
1342 else:
1343 d, f = os.path.split(path)
1344 b, x = os.path.splitext(f)
1345 if x == ".py":
1346 f = b
1347 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001348 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001349 self.module = imp.load_module("device_specific", *info)
1350 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001351 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001352
1353 def _DoCall(self, function_name, *args, **kwargs):
1354 """Call the named function in the device-specific module, passing
1355 the given args and kwargs. The first argument to the call will be
1356 the DeviceSpecific object itself. If there is no module, or the
1357 module does not define the function, return the value of the
1358 'default' kwarg (which itself defaults to None)."""
1359 if self.module is None or not hasattr(self.module, function_name):
1360 return kwargs.get("default", None)
1361 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1362
1363 def FullOTA_Assertions(self):
1364 """Called after emitting the block of assertions at the top of a
1365 full OTA package. Implementations can add whatever additional
1366 assertions they like."""
1367 return self._DoCall("FullOTA_Assertions")
1368
Doug Zongkere5ff5902012-01-17 10:55:37 -08001369 def FullOTA_InstallBegin(self):
1370 """Called at the start of full OTA installation."""
1371 return self._DoCall("FullOTA_InstallBegin")
1372
Doug Zongker05d3dea2009-06-22 11:32:31 -07001373 def FullOTA_InstallEnd(self):
1374 """Called at the end of full OTA installation; typically this is
1375 used to install the image for the device's baseband processor."""
1376 return self._DoCall("FullOTA_InstallEnd")
1377
1378 def IncrementalOTA_Assertions(self):
1379 """Called after emitting the block of assertions at the top of an
1380 incremental OTA package. Implementations can add whatever
1381 additional assertions they like."""
1382 return self._DoCall("IncrementalOTA_Assertions")
1383
Doug Zongkere5ff5902012-01-17 10:55:37 -08001384 def IncrementalOTA_VerifyBegin(self):
1385 """Called at the start of the verification phase of incremental
1386 OTA installation; additional checks can be placed here to abort
1387 the script before any changes are made."""
1388 return self._DoCall("IncrementalOTA_VerifyBegin")
1389
Doug Zongker05d3dea2009-06-22 11:32:31 -07001390 def IncrementalOTA_VerifyEnd(self):
1391 """Called at the end of the verification phase of incremental OTA
1392 installation; additional checks can be placed here to abort the
1393 script before any changes are made."""
1394 return self._DoCall("IncrementalOTA_VerifyEnd")
1395
Doug Zongkere5ff5902012-01-17 10:55:37 -08001396 def IncrementalOTA_InstallBegin(self):
1397 """Called at the start of incremental OTA installation (after
1398 verification is complete)."""
1399 return self._DoCall("IncrementalOTA_InstallBegin")
1400
Doug Zongker05d3dea2009-06-22 11:32:31 -07001401 def IncrementalOTA_InstallEnd(self):
1402 """Called at the end of incremental OTA installation; typically
1403 this is used to install the image for the device's baseband
1404 processor."""
1405 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001406
Tao Bao9bc6bb22015-11-09 16:58:28 -08001407 def VerifyOTA_Assertions(self):
1408 return self._DoCall("VerifyOTA_Assertions")
1409
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001410class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001411 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001412 self.name = name
1413 self.data = data
1414 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001415 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001416 self.sha1 = sha1(data).hexdigest()
1417
1418 @classmethod
1419 def FromLocalFile(cls, name, diskname):
1420 f = open(diskname, "rb")
1421 data = f.read()
1422 f.close()
1423 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001424
1425 def WriteToTemp(self):
1426 t = tempfile.NamedTemporaryFile()
1427 t.write(self.data)
1428 t.flush()
1429 return t
1430
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001431 def WriteToDir(self, d):
1432 with open(os.path.join(d, self.name), "wb") as fp:
1433 fp.write(self.data)
1434
Geremy Condra36bd3652014-02-06 19:45:10 -08001435 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001436 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001437
1438DIFF_PROGRAM_BY_EXT = {
1439 ".gz" : "imgdiff",
1440 ".zip" : ["imgdiff", "-z"],
1441 ".jar" : ["imgdiff", "-z"],
1442 ".apk" : ["imgdiff", "-z"],
1443 ".img" : "imgdiff",
1444 }
1445
1446class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001447 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001448 self.tf = tf
1449 self.sf = sf
1450 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001451 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001452
1453 def ComputePatch(self):
1454 """Compute the patch (as a string of data) needed to turn sf into
1455 tf. Returns the same tuple as GetPatch()."""
1456
1457 tf = self.tf
1458 sf = self.sf
1459
Doug Zongker24cd2802012-08-14 16:36:15 -07001460 if self.diff_program:
1461 diff_program = self.diff_program
1462 else:
1463 ext = os.path.splitext(tf.name)[1]
1464 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001465
1466 ttemp = tf.WriteToTemp()
1467 stemp = sf.WriteToTemp()
1468
1469 ext = os.path.splitext(tf.name)[1]
1470
1471 try:
1472 ptemp = tempfile.NamedTemporaryFile()
1473 if isinstance(diff_program, list):
1474 cmd = copy.copy(diff_program)
1475 else:
1476 cmd = [diff_program]
1477 cmd.append(stemp.name)
1478 cmd.append(ttemp.name)
1479 cmd.append(ptemp.name)
1480 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001481 err = []
1482 def run():
1483 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001484 if e:
1485 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001486 th = threading.Thread(target=run)
1487 th.start()
1488 th.join(timeout=300) # 5 mins
1489 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001490 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001491 p.terminate()
1492 th.join(5)
1493 if th.is_alive():
1494 p.kill()
1495 th.join()
1496
Tianjie Xua2a9f992018-01-05 15:15:54 -08001497 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001498 print("WARNING: failure running %s:\n%s\n" % (
1499 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001500 self.patch = None
1501 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001502 diff = ptemp.read()
1503 finally:
1504 ptemp.close()
1505 stemp.close()
1506 ttemp.close()
1507
1508 self.patch = diff
1509 return self.tf, self.sf, self.patch
1510
1511
1512 def GetPatch(self):
1513 """Return a tuple (target_file, source_file, patch_data).
1514 patch_data may be None if ComputePatch hasn't been called, or if
1515 computing the patch failed."""
1516 return self.tf, self.sf, self.patch
1517
1518
1519def ComputeDifferences(diffs):
1520 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001521 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001522
1523 # Do the largest files first, to try and reduce the long-pole effect.
1524 by_size = [(i.tf.size, i) for i in diffs]
1525 by_size.sort(reverse=True)
1526 by_size = [i[1] for i in by_size]
1527
1528 lock = threading.Lock()
1529 diff_iter = iter(by_size) # accessed under lock
1530
1531 def worker():
1532 try:
1533 lock.acquire()
1534 for d in diff_iter:
1535 lock.release()
1536 start = time.time()
1537 d.ComputePatch()
1538 dur = time.time() - start
1539 lock.acquire()
1540
1541 tf, sf, patch = d.GetPatch()
1542 if sf.name == tf.name:
1543 name = tf.name
1544 else:
1545 name = "%s (%s)" % (tf.name, sf.name)
1546 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001547 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001548 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001549 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1550 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001551 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001552 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001553 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001554 raise
1555
1556 # start worker threads; wait for them all to finish.
1557 threads = [threading.Thread(target=worker)
1558 for i in range(OPTIONS.worker_threads)]
1559 for th in threads:
1560 th.start()
1561 while threads:
1562 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001563
1564
Dan Albert8b72aef2015-03-23 19:13:21 -07001565class BlockDifference(object):
1566 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001567 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001568 self.tgt = tgt
1569 self.src = src
1570 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001571 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001572 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001573
Tao Baodd2a5892015-03-12 12:32:37 -07001574 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001575 version = max(
1576 int(i) for i in
1577 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001578 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001579 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001580
1581 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001582 version=self.version,
1583 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001584 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001585 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001586 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001587 self.touched_src_ranges = b.touched_src_ranges
1588 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001589
Tao Baoaac4ad52015-10-16 15:26:34 -07001590 if src is None:
1591 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1592 else:
1593 _, self.device = GetTypeAndDevice("/" + partition,
1594 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001595
Tao Baod8d14be2016-02-04 14:26:02 -08001596 @property
1597 def required_cache(self):
1598 return self._required_cache
1599
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001600 def WriteScript(self, script, output_zip, progress=None):
1601 if not self.src:
1602 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001603 script.Print("Patching %s image unconditionally..." % (self.partition,))
1604 else:
1605 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001606
Dan Albert8b72aef2015-03-23 19:13:21 -07001607 if progress:
1608 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001609 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001610 if OPTIONS.verify:
1611 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001612
Tao Bao9bc6bb22015-11-09 16:58:28 -08001613 def WriteStrictVerifyScript(self, script):
1614 """Verify all the blocks in the care_map, including clobbered blocks.
1615
1616 This differs from the WriteVerifyScript() function: a) it prints different
1617 error messages; b) it doesn't allow half-way updated images to pass the
1618 verification."""
1619
1620 partition = self.partition
1621 script.Print("Verifying %s..." % (partition,))
1622 ranges = self.tgt.care_map
1623 ranges_str = ranges.to_string_raw()
1624 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1625 'ui_print(" Verified.") || '
1626 'ui_print("\\"%s\\" has unexpected contents.");' % (
1627 self.device, ranges_str,
1628 self.tgt.TotalSha1(include_clobbered_blocks=True),
1629 self.device))
1630 script.AppendExtra("")
1631
Tao Baod522bdc2016-04-12 15:53:16 -07001632 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001633 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001634
1635 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001636 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001637 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001638
1639 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001640 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001641 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001642 ranges = self.touched_src_ranges
1643 expected_sha1 = self.touched_src_sha1
1644 else:
1645 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1646 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001647
1648 # No blocks to be checked, skipping.
1649 if not ranges:
1650 return
1651
Tao Bao5ece99d2015-05-12 11:42:31 -07001652 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001653 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1654 'block_image_verify("%s", '
1655 'package_extract_file("%s.transfer.list"), '
1656 '"%s.new.dat", "%s.patch.dat")) then') % (
1657 self.device, ranges_str, expected_sha1,
1658 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001659 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001660 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001661
Tianjie Xufc3422a2015-12-15 11:53:59 -08001662 if self.version >= 4:
1663
1664 # Bug: 21124327
1665 # When generating incrementals for the system and vendor partitions in
1666 # version 4 or newer, explicitly check the first block (which contains
1667 # the superblock) of the partition to see if it's what we expect. If
1668 # this check fails, give an explicit log message about the partition
1669 # having been remounted R/W (the most likely explanation).
1670 if self.check_first_block:
1671 script.AppendExtra('check_first_block("%s");' % (self.device,))
1672
1673 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001674 if partition == "system":
1675 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1676 else:
1677 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001678 script.AppendExtra((
1679 'ifelse (block_image_recover("{device}", "{ranges}") && '
1680 'block_image_verify("{device}", '
1681 'package_extract_file("{partition}.transfer.list"), '
1682 '"{partition}.new.dat", "{partition}.patch.dat"), '
1683 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001684 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001685 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001686 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001687
Tao Baodd2a5892015-03-12 12:32:37 -07001688 # Abort the OTA update. Note that the incremental OTA cannot be applied
1689 # even if it may match the checksum of the target partition.
1690 # a) If version < 3, operations like move and erase will make changes
1691 # unconditionally and damage the partition.
1692 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001693 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001694 if partition == "system":
1695 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1696 else:
1697 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1698 script.AppendExtra((
1699 'abort("E%d: %s partition has unexpected contents");\n'
1700 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001701
Tao Bao5fcaaef2015-06-01 13:40:49 -07001702 def _WritePostInstallVerifyScript(self, script):
1703 partition = self.partition
1704 script.Print('Verifying the updated %s image...' % (partition,))
1705 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1706 ranges = self.tgt.care_map
1707 ranges_str = ranges.to_string_raw()
1708 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1709 self.device, ranges_str,
1710 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001711
1712 # Bug: 20881595
1713 # Verify that extended blocks are really zeroed out.
1714 if self.tgt.extended:
1715 ranges_str = self.tgt.extended.to_string_raw()
1716 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1717 self.device, ranges_str,
1718 self._HashZeroBlocks(self.tgt.extended.size())))
1719 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001720 if partition == "system":
1721 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1722 else:
1723 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001724 script.AppendExtra(
1725 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001726 ' abort("E%d: %s partition has unexpected non-zero contents after '
1727 'OTA update");\n'
1728 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001729 else:
1730 script.Print('Verified the updated %s image.' % (partition,))
1731
Tianjie Xu209db462016-05-24 17:34:52 -07001732 if partition == "system":
1733 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1734 else:
1735 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1736
Tao Bao5fcaaef2015-06-01 13:40:49 -07001737 script.AppendExtra(
1738 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001739 ' abort("E%d: %s partition has unexpected contents after OTA '
1740 'update");\n'
1741 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001742
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001743 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001744 ZipWrite(output_zip,
1745 '{}.transfer.list'.format(self.path),
1746 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001747
1748 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1749 # almost triples the compression time but doesn't further reduce the size too much.
1750 # For a typical 1.8G system.new.dat
1751 # zip | brotli(quality 6) | brotli(quality 9)
1752 # compressed_size: 942M | 869M (~8% reduced) | 854M
1753 # compression_time: 75s | 265s | 719s
1754 # decompression_time: 15s | 25s | 25s
1755
1756 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001757 brotli_cmd = ['brotli', '--quality=6',
1758 '--output={}.new.dat.br'.format(self.path),
1759 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001760 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao80921982018-03-21 21:02:19 -07001761 p = Run(brotli_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1762 stdoutdata, _ = p.communicate()
1763 assert p.returncode == 0, \
1764 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1765 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001766
1767 new_data_name = '{}.new.dat.br'.format(self.partition)
1768 ZipWrite(output_zip,
1769 '{}.new.dat.br'.format(self.path),
1770 new_data_name,
1771 compress_type=zipfile.ZIP_STORED)
1772 else:
1773 new_data_name = '{}.new.dat'.format(self.partition)
1774 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1775
Dan Albert8e0178d2015-01-27 15:53:15 -08001776 ZipWrite(output_zip,
1777 '{}.patch.dat'.format(self.path),
1778 '{}.patch.dat'.format(self.partition),
1779 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001780
Tianjie Xu209db462016-05-24 17:34:52 -07001781 if self.partition == "system":
1782 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1783 else:
1784 code = ErrorCode.VENDOR_UPDATE_FAILURE
1785
Dan Albert8e0178d2015-01-27 15:53:15 -08001786 call = ('block_image_update("{device}", '
1787 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001788 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001789 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001790 device=self.device, partition=self.partition,
1791 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001792 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001793
Dan Albert8b72aef2015-03-23 19:13:21 -07001794 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001795 data = source.ReadRangeSet(ranges)
1796 ctx = sha1()
1797
1798 for p in data:
1799 ctx.update(p)
1800
1801 return ctx.hexdigest()
1802
Tao Baoe9b61912015-07-09 17:37:49 -07001803 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1804 """Return the hash value for all zero blocks."""
1805 zero_block = '\x00' * 4096
1806 ctx = sha1()
1807 for _ in range(num_blocks):
1808 ctx.update(zero_block)
1809
1810 return ctx.hexdigest()
1811
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001812
1813DataImage = blockimgdiff.DataImage
1814
Doug Zongker96a57e72010-09-26 14:57:41 -07001815# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001816PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001817 "ext4": "EMMC",
1818 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001819 "f2fs": "EMMC",
1820 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001821}
Doug Zongker96a57e72010-09-26 14:57:41 -07001822
1823def GetTypeAndDevice(mount_point, info):
1824 fstab = info["fstab"]
1825 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001826 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1827 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001828 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001829 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001830
1831
1832def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001833 """Parses and converts a PEM-encoded certificate into DER-encoded.
1834
1835 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1836
1837 Returns:
1838 The decoded certificate string.
1839 """
1840 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001841 save = False
1842 for line in data.split("\n"):
1843 if "--END CERTIFICATE--" in line:
1844 break
1845 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001846 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001847 if "--BEGIN CERTIFICATE--" in line:
1848 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001849 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001850 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001851
Tao Bao04e1f012018-02-04 12:13:35 -08001852
1853def ExtractPublicKey(cert):
1854 """Extracts the public key (PEM-encoded) from the given certificate file.
1855
1856 Args:
1857 cert: The certificate filename.
1858
1859 Returns:
1860 The public key string.
1861
1862 Raises:
1863 AssertionError: On non-zero return from 'openssl'.
1864 """
1865 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1866 # While openssl 1.1 writes the key into the given filename followed by '-out',
1867 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1868 # stdout instead.
1869 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1870 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1871 pubkey, stderrdata = proc.communicate()
1872 assert proc.returncode == 0, \
1873 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1874 return pubkey
1875
1876
Doug Zongker412c02f2014-02-13 10:58:24 -08001877def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1878 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001879 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001880
Tao Bao6d5d6232018-03-09 17:04:42 -08001881 Most of the space in the boot and recovery images is just the kernel, which is
1882 identical for the two, so the resulting patch should be efficient. Add it to
1883 the output zip, along with a shell script that is run from init.rc on first
1884 boot to actually do the patching and install the new recovery image.
1885
1886 Args:
1887 input_dir: The top-level input directory of the target-files.zip.
1888 output_sink: The callback function that writes the result.
1889 recovery_img: File object for the recovery image.
1890 boot_img: File objects for the boot image.
1891 info_dict: A dict returned by common.LoadInfoDict() on the input
1892 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001893 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001894 if info_dict is None:
1895 info_dict = OPTIONS.info_dict
1896
Tao Bao6d5d6232018-03-09 17:04:42 -08001897 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001898
Tao Baof2cffbd2015-07-22 12:33:18 -07001899 if full_recovery_image:
1900 output_sink("etc/recovery.img", recovery_img.data)
1901
1902 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08001903 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07001904 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08001905 # With system-root-image, boot and recovery images will have mismatching
1906 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
1907 # to handle such a case.
1908 if system_root_image:
1909 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07001910 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08001911 assert not os.path.exists(path)
1912 else:
1913 diff_program = ["imgdiff"]
1914 if os.path.exists(path):
1915 diff_program.append("-b")
1916 diff_program.append(path)
1917 bonus_args = "-b /system/etc/recovery-resource.dat"
1918 else:
1919 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07001920
1921 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1922 _, _, patch = d.ComputePatch()
1923 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001924
Dan Albertebb19aa2015-03-27 19:11:53 -07001925 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001926 # The following GetTypeAndDevice()s need to use the path in the target
1927 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001928 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1929 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1930 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001931 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001932
Tao Baof2cffbd2015-07-22 12:33:18 -07001933 if full_recovery_image:
1934 sh = """#!/system/bin/sh
1935if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1936 applypatch /system/etc/recovery.img %(type)s:%(device)s %(sha1)s %(size)d && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1937else
1938 log -t recovery "Recovery image already installed"
1939fi
1940""" % {'type': recovery_type,
1941 'device': recovery_device,
1942 'sha1': recovery_img.sha1,
1943 'size': recovery_img.size}
1944 else:
1945 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001946if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1947 applypatch %(bonus_args)s %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1948else
1949 log -t recovery "Recovery image already installed"
1950fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001951""" % {'boot_size': boot_img.size,
1952 'boot_sha1': boot_img.sha1,
1953 'recovery_size': recovery_img.size,
1954 'recovery_sha1': recovery_img.sha1,
1955 'boot_type': boot_type,
1956 'boot_device': boot_device,
1957 'recovery_type': recovery_type,
1958 'recovery_device': recovery_device,
1959 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001960
1961 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001962 # in the L release.
1963 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001964
Tao Bao89fbb0f2017-01-10 10:47:58 -08001965 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001966
1967 output_sink(sh_location, sh)