blob: d0ee6ae4db85e2e37368dd2a61cc33330c53e770 [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
28import subprocess
29import sys
30import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070031import threading
32import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070033import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080034from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070035
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070036import blockimgdiff
37
Dan Albert8b72aef2015-03-23 19:13:21 -070038class Options(object):
39 def __init__(self):
40 platform_search_path = {
41 "linux2": "out/host/linux-x86",
42 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070043 }
Doug Zongker85448772014-09-09 14:59:20 -070044
Dan Albert8b72aef2015-03-23 19:13:21 -070045 self.search_path = platform_search_path.get(sys.platform, None)
46 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080047 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.extra_signapk_args = []
49 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080050 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070051 self.public_key_suffix = ".x509.pem"
52 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070053 # use otatools built boot_signer by default
54 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070055 self.boot_signer_args = []
56 self.verity_signer_path = None
57 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070058 self.verbose = False
59 self.tempfiles = []
60 self.device_specific = None
61 self.extras = {}
62 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070063 self.source_info_dict = None
64 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070065 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070066 # Stash size cannot exceed cache_size * threshold.
67 self.cache_size = None
68 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070069
70
71OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070072
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080073
74# Values for "certificate" in apkcerts that mean special things.
75SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
76
Tao Bao9dd909e2017-11-14 11:27:32 -080077
78# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Jaekyun Seokb7735d82017-11-27 17:04:47 +090079AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product', 'dtbo')
Tao Bao9dd909e2017-11-14 11:27:32 -080080
81
Tianjie Xu209db462016-05-24 17:34:52 -070082class ErrorCode(object):
83 """Define error_codes for failures that happen during the actual
84 update package installation.
85
86 Error codes 0-999 are reserved for failures before the package
87 installation (i.e. low battery, package verification failure).
88 Detailed code in 'bootable/recovery/error_code.h' """
89
90 SYSTEM_VERIFICATION_FAILURE = 1000
91 SYSTEM_UPDATE_FAILURE = 1001
92 SYSTEM_UNEXPECTED_CONTENTS = 1002
93 SYSTEM_NONZERO_CONTENTS = 1003
94 SYSTEM_RECOVER_FAILURE = 1004
95 VENDOR_VERIFICATION_FAILURE = 2000
96 VENDOR_UPDATE_FAILURE = 2001
97 VENDOR_UNEXPECTED_CONTENTS = 2002
98 VENDOR_NONZERO_CONTENTS = 2003
99 VENDOR_RECOVER_FAILURE = 2004
100 OEM_PROP_MISMATCH = 3000
101 FINGERPRINT_MISMATCH = 3001
102 THUMBPRINT_MISMATCH = 3002
103 OLDER_BUILD = 3003
104 DEVICE_MISMATCH = 3004
105 BAD_PATCH_FILE = 3005
106 INSUFFICIENT_CACHE_SPACE = 3006
107 TUNE_PARTITION_FAILURE = 3007
108 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800109
Dan Albert8b72aef2015-03-23 19:13:21 -0700110class ExternalError(RuntimeError):
111 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700112
113
Tao Bao39451582017-05-04 11:10:47 -0700114def Run(args, verbose=None, **kwargs):
115 """Create and return a subprocess.Popen object.
116
117 Caller can specify if the command line should be printed. The global
118 OPTIONS.verbose will be used if not specified.
119 """
120 if verbose is None:
121 verbose = OPTIONS.verbose
122 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800123 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700124 return subprocess.Popen(args, **kwargs)
125
126
Ying Wang7e6d4e42010-12-13 16:25:36 -0800127def CloseInheritedPipes():
128 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
129 before doing other work."""
130 if platform.system() != "Darwin":
131 return
132 for d in range(3, 1025):
133 try:
134 stat = os.fstat(d)
135 if stat is not None:
136 pipebit = stat[0] & 0x1000
137 if pipebit != 0:
138 os.close(d)
139 except OSError:
140 pass
141
142
Tao Bao2c15d9e2015-07-09 11:51:16 -0700143def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700144 """Read and parse the META/misc_info.txt key/value pairs from the
145 input target files and return a dict."""
146
Doug Zongkerc9253822014-02-04 12:17:58 -0800147 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700148 if isinstance(input_file, zipfile.ZipFile):
149 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800150 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700151 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800152 try:
153 with open(path) as f:
154 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700155 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800156 if e.errno == errno.ENOENT:
157 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800158
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700159 try:
Michael Runge6e836112014-04-15 17:40:21 -0700160 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700161 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800162 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700163
Tao Bao6cd54732017-02-27 15:12:05 -0800164 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800165 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800166
Tao Bao84e75682015-07-19 02:38:53 -0700167 # A few properties are stored as links to the files in the out/ directory.
168 # It works fine with the build system. However, they are no longer available
169 # when (re)generating from target_files zip. If input_dir is not None, we
170 # are doing repacking. Redirect those properties to the actual files in the
171 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700172 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400173 # We carry a copy of file_contexts.bin under META/. If not available,
174 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700175 # to build images than the one running on device, such as when enabling
176 # system_root_image. In that case, we must have the one for image
177 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700178 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
179 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700180 if d.get("system_root_image") == "true":
181 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700182 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700183 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700184 if not os.path.exists(fc_config):
185 fc_config = None
186
187 if fc_config:
188 d["selinux_fc"] = fc_config
189
Tao Bao84e75682015-07-19 02:38:53 -0700190 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
191 if d.get("system_root_image") == "true":
192 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
193 d["ramdisk_fs_config"] = os.path.join(
194 input_dir, "META", "root_filesystem_config.txt")
195
Tao Baof54216f2016-03-29 15:12:37 -0700196 # Redirect {system,vendor}_base_fs_file.
197 if "system_base_fs_file" in d:
198 basename = os.path.basename(d["system_base_fs_file"])
199 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700200 if os.path.exists(system_base_fs_file):
201 d["system_base_fs_file"] = system_base_fs_file
202 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800203 print("Warning: failed to find system base fs file: %s" % (
204 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700205 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700206
207 if "vendor_base_fs_file" in d:
208 basename = os.path.basename(d["vendor_base_fs_file"])
209 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700210 if os.path.exists(vendor_base_fs_file):
211 d["vendor_base_fs_file"] = vendor_base_fs_file
212 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800213 print("Warning: failed to find vendor base fs file: %s" % (
214 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700215 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700216
Doug Zongker37974732010-09-16 17:44:38 -0700217 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800218 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700219 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700220 if not line:
221 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700222 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700223 if not value:
224 continue
Doug Zongker37974732010-09-16 17:44:38 -0700225 if name == "blocksize":
226 d[name] = value
227 else:
228 d[name + "_size"] = value
229 except KeyError:
230 pass
231
232 def makeint(key):
233 if key in d:
234 d[key] = int(d[key], 0)
235
236 makeint("recovery_api_version")
237 makeint("blocksize")
238 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700239 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700240 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700241 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700242 makeint("recovery_size")
243 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800244 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700245
Tianjie Xucfa86222016-03-07 16:31:19 -0800246 system_root_image = d.get("system_root_image", None) == "true"
247 if d.get("no_recovery", None) != "true":
248 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800249 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800250 recovery_fstab_path, system_root_image)
251 elif d.get("recovery_as_boot", None) == "true":
252 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
253 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
254 recovery_fstab_path, system_root_image)
255 else:
256 d["fstab"] = None
257
Tao Baobcd1d162017-08-26 13:10:26 -0700258 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
259 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Tao Bao12d87fc2018-01-31 12:18:52 -0800260
261 # Set up the salt (based on fingerprint or thumbprint) that will be used when
262 # adding AVB footer.
263 if d.get("avb_enable") == "true":
264 fp = None
265 if "build.prop" in d:
266 build_prop = d["build.prop"]
267 if "ro.build.fingerprint" in build_prop:
268 fp = build_prop["ro.build.fingerprint"]
269 elif "ro.build.thumbprint" in build_prop:
270 fp = build_prop["ro.build.thumbprint"]
271 if fp:
272 d["avb_salt"] = sha256(fp).hexdigest()
273
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700274 return d
275
Tao Baod1de6f32017-03-01 16:38:48 -0800276
Tao Baobcd1d162017-08-26 13:10:26 -0700277def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700278 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700279 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700280 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700281 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700282 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700283 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700284
Tao Baod1de6f32017-03-01 16:38:48 -0800285
Michael Runge6e836112014-04-15 17:40:21 -0700286def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700287 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700288 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700289 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700290 if not line or line.startswith("#"):
291 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700292 if "=" in line:
293 name, value = line.split("=", 1)
294 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700295 return d
296
Tao Baod1de6f32017-03-01 16:38:48 -0800297
Tianjie Xucfa86222016-03-07 16:31:19 -0800298def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
299 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700300 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800301 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700302 self.mount_point = mount_point
303 self.fs_type = fs_type
304 self.device = device
305 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700306 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700307
308 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800309 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700310 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800311 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700312 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700313
Tao Baod1de6f32017-03-01 16:38:48 -0800314 assert fstab_version == 2
315
316 d = {}
317 for line in data.split("\n"):
318 line = line.strip()
319 if not line or line.startswith("#"):
320 continue
321
322 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
323 pieces = line.split()
324 if len(pieces) != 5:
325 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
326
327 # Ignore entries that are managed by vold.
328 options = pieces[4]
329 if "voldmanaged=" in options:
330 continue
331
332 # It's a good line, parse it.
333 length = 0
334 options = options.split(",")
335 for i in options:
336 if i.startswith("length="):
337 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800338 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800339 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700340 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800341
Tao Baod1de6f32017-03-01 16:38:48 -0800342 mount_flags = pieces[3]
343 # Honor the SELinux context if present.
344 context = None
345 for i in mount_flags.split(","):
346 if i.startswith("context="):
347 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800348
Tao Baod1de6f32017-03-01 16:38:48 -0800349 mount_point = pieces[1]
350 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
351 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800352
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700353 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700354 # system. Other areas assume system is always at "/system" so point /system
355 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700356 if system_root_image:
357 assert not d.has_key("/system") and d.has_key("/")
358 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700359 return d
360
361
Doug Zongker37974732010-09-16 17:44:38 -0700362def DumpInfoDict(d):
363 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800364 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700365
Dan Albert8b72aef2015-03-23 19:13:21 -0700366
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800367def AppendAVBSigningArgs(cmd, partition):
368 """Append signing arguments for avbtool."""
369 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
370 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
371 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
372 if key_path and algorithm:
373 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700374 avb_salt = OPTIONS.info_dict.get("avb_salt")
375 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
376 if avb_salt and partition != "vbmeta":
377 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800378
379
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700380def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800381 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700382 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700383
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700384 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800385 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
386 we are building a two-step special image (i.e. building a recovery image to
387 be loaded into /boot in two-step OTAs).
388
389 Return the image data, or None if sourcedir does not appear to contains files
390 for building the requested image.
391 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700392
393 def make_ramdisk():
394 ramdisk_img = tempfile.NamedTemporaryFile()
395
396 if os.access(fs_config_file, os.F_OK):
397 cmd = ["mkbootfs", "-f", fs_config_file,
398 os.path.join(sourcedir, "RAMDISK")]
399 else:
400 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
401 p1 = Run(cmd, stdout=subprocess.PIPE)
402 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
403
404 p2.wait()
405 p1.wait()
406 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
407 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
408
409 return ramdisk_img
410
411 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
412 return None
413
414 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700415 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700416
Doug Zongkerd5131602012-08-02 14:46:42 -0700417 if info_dict is None:
418 info_dict = OPTIONS.info_dict
419
Doug Zongkereef39442009-04-02 12:14:19 -0700420 img = tempfile.NamedTemporaryFile()
421
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700422 if has_ramdisk:
423 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700424
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800425 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
426 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
427
428 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700429
Benoit Fradina45a8682014-07-14 21:00:43 +0200430 fn = os.path.join(sourcedir, "second")
431 if os.access(fn, os.F_OK):
432 cmd.append("--second")
433 cmd.append(fn)
434
Doug Zongker171f1cd2009-06-15 22:36:37 -0700435 fn = os.path.join(sourcedir, "cmdline")
436 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700437 cmd.append("--cmdline")
438 cmd.append(open(fn).read().rstrip("\n"))
439
440 fn = os.path.join(sourcedir, "base")
441 if os.access(fn, os.F_OK):
442 cmd.append("--base")
443 cmd.append(open(fn).read().rstrip("\n"))
444
Ying Wang4de6b5b2010-08-25 14:29:34 -0700445 fn = os.path.join(sourcedir, "pagesize")
446 if os.access(fn, os.F_OK):
447 cmd.append("--pagesize")
448 cmd.append(open(fn).read().rstrip("\n"))
449
Doug Zongkerd5131602012-08-02 14:46:42 -0700450 args = info_dict.get("mkbootimg_args", None)
451 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700452 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700453
Sami Tolvanen3303d902016-03-15 16:49:30 +0000454 args = info_dict.get("mkbootimg_version_args", None)
455 if args and args.strip():
456 cmd.extend(shlex.split(args))
457
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700458 if has_ramdisk:
459 cmd.extend(["--ramdisk", ramdisk_img.name])
460
Tao Baod95e9fd2015-03-29 23:07:41 -0700461 img_unsigned = None
462 if info_dict.get("vboot", None):
463 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700464 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700465 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700466 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700467
Tao Baobf70c3182017-07-11 17:27:55 -0700468 # "boot" or "recovery", without extension.
469 partition_name = os.path.basename(sourcedir).lower()
470
Doug Zongker38a649f2009-06-17 09:07:09 -0700471 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700472 p.communicate()
Tao Baobf70c3182017-07-11 17:27:55 -0700473 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700474
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100475 if (info_dict.get("boot_signer", None) == "true" and
476 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800477 # Hard-code the path as "/boot" for two-step special recovery image (which
478 # will be loaded into /boot during the two-step OTA).
479 if two_step_image:
480 path = "/boot"
481 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700482 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700483 cmd = [OPTIONS.boot_signer_path]
484 cmd.extend(OPTIONS.boot_signer_args)
485 cmd.extend([path, img.name,
486 info_dict["verity_key"] + ".pk8",
487 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700488 p = Run(cmd, stdout=subprocess.PIPE)
489 p.communicate()
490 assert p.returncode == 0, "boot_signer of %s image failed" % path
491
Tao Baod95e9fd2015-03-29 23:07:41 -0700492 # Sign the image if vboot is non-empty.
493 elif info_dict.get("vboot", None):
Tao Baobf70c3182017-07-11 17:27:55 -0700494 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700495 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800496 # We have switched from the prebuilt futility binary to using the tool
497 # (futility-host) built from the source. Override the setting in the old
498 # TF.zip.
499 futility = info_dict["futility"]
500 if futility.startswith("prebuilts/"):
501 futility = "futility-host"
502 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700503 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700504 info_dict["vboot_key"] + ".vbprivk",
505 info_dict["vboot_subkey"] + ".vbprivk",
506 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700507 img.name]
508 p = Run(cmd, stdout=subprocess.PIPE)
509 p.communicate()
510 assert p.returncode == 0, "vboot_signer of %s image failed" % path
511
Tao Baof3282b42015-04-01 11:21:55 -0700512 # Clean up the temp files.
513 img_unsigned.close()
514 img_keyblock.close()
515
David Zeuthen8fecb282017-12-01 16:24:01 -0500516 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800517 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700518 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500519 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400520 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700521 "--partition_size", str(part_size), "--partition_name",
522 partition_name]
523 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500524 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400525 if args and args.strip():
526 cmd.extend(shlex.split(args))
527 p = Run(cmd, stdout=subprocess.PIPE)
528 p.communicate()
529 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c3182017-07-11 17:27:55 -0700530 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500531
532 img.seek(os.SEEK_SET, 0)
533 data = img.read()
534
535 if has_ramdisk:
536 ramdisk_img.close()
537 img.close()
538
539 return data
540
541
Doug Zongkerd5131602012-08-02 14:46:42 -0700542def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800543 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700544 """Return a File object with the desired bootable image.
545
546 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
547 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
548 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700549
Doug Zongker55d93282011-01-25 17:03:34 -0800550 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
551 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800552 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800553 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700554
555 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
556 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800557 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700558 return File.FromLocalFile(name, prebuilt_path)
559
Tao Bao89fbb0f2017-01-10 10:47:58 -0800560 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700561
562 if info_dict is None:
563 info_dict = OPTIONS.info_dict
564
565 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800566 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
567 # for recovery.
568 has_ramdisk = (info_dict.get("system_root_image") != "true" or
569 prebuilt_name != "boot.img" or
570 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700571
Doug Zongker6f1d0312014-08-22 08:07:12 -0700572 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400573 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
574 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800575 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700576 if data:
577 return File(name, data)
578 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800579
Doug Zongkereef39442009-04-02 12:14:19 -0700580
Narayan Kamatha07bf042017-08-14 14:49:21 +0100581def Gunzip(in_filename, out_filename):
582 """Gunzip the given gzip compressed file to a given output file.
583 """
584 with gzip.open(in_filename, "rb") as in_file, open(out_filename, "wb") as out_file:
585 shutil.copyfileobj(in_file, out_file)
586
587
Doug Zongker75f17362009-12-08 13:46:44 -0800588def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800589 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800590
Tao Bao1c830bf2017-12-25 10:43:47 -0800591 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
592 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800593
Tao Bao1c830bf2017-12-25 10:43:47 -0800594 Returns:
595 (tempdir, zipobj): tempdir is the name of the temprary directory; zipobj is
596 a zipfile.ZipFile (of the main file), open for reading.
Doug Zongker55d93282011-01-25 17:03:34 -0800597 """
Doug Zongkereef39442009-04-02 12:14:19 -0700598
Doug Zongker55d93282011-01-25 17:03:34 -0800599 def unzip_to_dir(filename, dirname):
600 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
601 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800602 cmd.extend(pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800603 p = Run(cmd, stdout=subprocess.PIPE)
604 p.communicate()
605 if p.returncode != 0:
606 raise ExternalError("failed to unzip input target-files \"%s\"" %
607 (filename,))
608
Tao Bao1c830bf2017-12-25 10:43:47 -0800609 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800610 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
611 if m:
612 unzip_to_dir(m.group(1), tmp)
613 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
614 filename = m.group(1)
615 else:
616 unzip_to_dir(filename, tmp)
617
618 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700619
620
621def GetKeyPasswords(keylist):
622 """Given a list of keys, prompt the user to enter passwords for
623 those which require them. Return a {key: password} dict. password
624 will be None if the key has no password."""
625
Doug Zongker8ce7c252009-05-22 13:34:54 -0700626 no_passwords = []
627 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700628 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700629 devnull = open("/dev/null", "w+b")
630 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800631 # We don't need a password for things that aren't really keys.
632 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700633 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700634 continue
635
T.R. Fullhart37e10522013-03-18 10:31:26 -0700636 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700637 "-inform", "DER", "-nocrypt"],
638 stdin=devnull.fileno(),
639 stdout=devnull.fileno(),
640 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700641 p.communicate()
642 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700643 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700644 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700645 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700646 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
647 "-inform", "DER", "-passin", "pass:"],
648 stdin=devnull.fileno(),
649 stdout=devnull.fileno(),
650 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700651 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700652 if p.returncode == 0:
653 # Encrypted key with empty string as password.
654 key_passwords[k] = ''
655 elif stderr.startswith('Error decrypting key'):
656 # Definitely encrypted key.
657 # It would have said "Error reading key" if it didn't parse correctly.
658 need_passwords.append(k)
659 else:
660 # Potentially, a type of key that openssl doesn't understand.
661 # We'll let the routines in signapk.jar handle it.
662 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700663 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700664
T.R. Fullhart37e10522013-03-18 10:31:26 -0700665 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700666 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700667 return key_passwords
668
669
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800670def GetMinSdkVersion(apk_name):
671 """Get the minSdkVersion delared in the APK. This can be both a decimal number
672 (API Level) or a codename.
673 """
674
675 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
676 output, err = p.communicate()
677 if err:
678 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
679 % (p.returncode,))
680
681 for line in output.split("\n"):
682 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
683 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
684 if m:
685 return m.group(1)
686 raise ExternalError("No minSdkVersion returned by aapt")
687
688
689def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
690 """Get the minSdkVersion declared in the APK as a number (API Level). If
691 minSdkVersion is set to a codename, it is translated to a number using the
692 provided map.
693 """
694
695 version = GetMinSdkVersion(apk_name)
696 try:
697 return int(version)
698 except ValueError:
699 # Not a decimal number. Codename?
700 if version in codename_to_api_level_map:
701 return codename_to_api_level_map[version]
702 else:
703 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
704 % (version, codename_to_api_level_map))
705
706
707def SignFile(input_name, output_name, key, password, min_api_level=None,
708 codename_to_api_level_map=dict(),
709 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700710 """Sign the input_name zip/jar/apk, producing output_name. Use the
711 given key and password (the latter may be None if the key does not
712 have a password.
713
Doug Zongker951495f2009-08-14 12:44:19 -0700714 If whole_file is true, use the "-w" option to SignApk to embed a
715 signature that covers the whole file in the archive comment of the
716 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800717
718 min_api_level is the API Level (int) of the oldest platform this file may end
719 up on. If not specified for an APK, the API Level is obtained by interpreting
720 the minSdkVersion attribute of the APK's AndroidManifest.xml.
721
722 codename_to_api_level_map is needed to translate the codename which may be
723 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700724 """
Doug Zongker951495f2009-08-14 12:44:19 -0700725
Alex Klyubin9667b182015-12-10 13:38:50 -0800726 java_library_path = os.path.join(
727 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
728
Tao Baoe95540e2016-11-08 12:08:53 -0800729 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
730 ["-Djava.library.path=" + java_library_path,
731 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
732 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700733 if whole_file:
734 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800735
736 min_sdk_version = min_api_level
737 if min_sdk_version is None:
738 if not whole_file:
739 min_sdk_version = GetMinSdkVersionInt(
740 input_name, codename_to_api_level_map)
741 if min_sdk_version is not None:
742 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
743
T.R. Fullhart37e10522013-03-18 10:31:26 -0700744 cmd.extend([key + OPTIONS.public_key_suffix,
745 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800746 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700747
748 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700749 if password is not None:
750 password += "\n"
751 p.communicate(password)
752 if p.returncode != 0:
753 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
754
Doug Zongkereef39442009-04-02 12:14:19 -0700755
Doug Zongker37974732010-09-16 17:44:38 -0700756def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800757 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700758
Tao Bao9dd909e2017-11-14 11:27:32 -0800759 For non-AVB images, raise exception if the data is too big. Print a warning
760 if the data is nearing the maximum size.
761
762 For AVB images, the actual image size should be identical to the limit.
763
764 Args:
765 data: A string that contains all the data for the partition.
766 target: The partition name. The ".img" suffix is optional.
767 info_dict: The dict to be looked up for relevant info.
768 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700769 if target.endswith(".img"):
770 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700771 mount_point = "/" + target
772
Ying Wangf8824af2014-06-03 14:07:27 -0700773 fs_type = None
774 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700775 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700776 if mount_point == "/userdata":
777 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700778 p = info_dict["fstab"][mount_point]
779 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800780 device = p.device
781 if "/" in device:
782 device = device[device.rfind("/")+1:]
783 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700784 if not fs_type or not limit:
785 return
Doug Zongkereef39442009-04-02 12:14:19 -0700786
Andrew Boie0f9aec82012-02-14 09:32:52 -0800787 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800788 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
789 # path.
790 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
791 if size != limit:
792 raise ExternalError(
793 "Mismatching image size for %s: expected %d actual %d" % (
794 target, limit, size))
795 else:
796 pct = float(size) * 100.0 / limit
797 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
798 if pct >= 99.0:
799 raise ExternalError(msg)
800 elif pct >= 95.0:
801 print("\n WARNING: %s\n" % (msg,))
802 elif OPTIONS.verbose:
803 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700804
805
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800806def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800807 """Parses the APK certs info from a given target-files zip.
808
809 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
810 tuple with the following elements: (1) a dictionary that maps packages to
811 certs (based on the "certificate" and "private_key" attributes in the file;
812 (2) a string representing the extension of compressed APKs in the target files
813 (e.g ".gz", ".bro").
814
815 Args:
816 tf_zip: The input target_files ZipFile (already open).
817
818 Returns:
819 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
820 the extension string of compressed APKs (e.g. ".gz"), or None if there's
821 no compressed APKs.
822 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800823 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100824 compressed_extension = None
825
Tao Bao0f990332017-09-08 19:02:54 -0700826 # META/apkcerts.txt contains the info for _all_ the packages known at build
827 # time. Filter out the ones that are not installed.
828 installed_files = set()
829 for name in tf_zip.namelist():
830 basename = os.path.basename(name)
831 if basename:
832 installed_files.add(basename)
833
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800834 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
835 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700836 if not line:
837 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800838 m = re.match(
839 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
840 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
841 line)
842 if not m:
843 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100844
Tao Bao818ddf52018-01-05 11:17:34 -0800845 matches = m.groupdict()
846 cert = matches["CERT"]
847 privkey = matches["PRIVKEY"]
848 name = matches["NAME"]
849 this_compressed_extension = matches["COMPRESSED"]
850
851 public_key_suffix_len = len(OPTIONS.public_key_suffix)
852 private_key_suffix_len = len(OPTIONS.private_key_suffix)
853 if cert in SPECIAL_CERT_STRINGS and not privkey:
854 certmap[name] = cert
855 elif (cert.endswith(OPTIONS.public_key_suffix) and
856 privkey.endswith(OPTIONS.private_key_suffix) and
857 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
858 certmap[name] = cert[:-public_key_suffix_len]
859 else:
860 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
861
862 if not this_compressed_extension:
863 continue
864
865 # Only count the installed files.
866 filename = name + '.' + this_compressed_extension
867 if filename not in installed_files:
868 continue
869
870 # Make sure that all the values in the compression map have the same
871 # extension. We don't support multiple compression methods in the same
872 # system image.
873 if compressed_extension:
874 if this_compressed_extension != compressed_extension:
875 raise ValueError(
876 "Multiple compressed extensions: {} vs {}".format(
877 compressed_extension, this_compressed_extension))
878 else:
879 compressed_extension = this_compressed_extension
880
881 return (certmap,
882 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800883
884
Doug Zongkereef39442009-04-02 12:14:19 -0700885COMMON_DOCSTRING = """
886 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700887 Prepend <dir>/bin to the list of places to search for binaries
888 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700889
Doug Zongker05d3dea2009-06-22 11:32:31 -0700890 -s (--device_specific) <file>
891 Path to the python module containing device-specific
892 releasetools code.
893
Doug Zongker8bec09e2009-11-30 15:37:14 -0800894 -x (--extra) <key=value>
895 Add a key/value pair to the 'extras' dict, which device-specific
896 extension code may look at.
897
Doug Zongkereef39442009-04-02 12:14:19 -0700898 -v (--verbose)
899 Show command lines being executed.
900
901 -h (--help)
902 Display this usage message and exit.
903"""
904
905def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800906 print(docstring.rstrip("\n"))
907 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700908
909
910def ParseOptions(argv,
911 docstring,
912 extra_opts="", extra_long_opts=(),
913 extra_option_handler=None):
914 """Parse the options in argv and return any arguments that aren't
915 flags. docstring is the calling module's docstring, to be displayed
916 for errors and -h. extra_opts and extra_long_opts are for flags
917 defined by the caller, which are processed by passing them to
918 extra_option_handler."""
919
920 try:
921 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800922 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800923 ["help", "verbose", "path=", "signapk_path=",
924 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700925 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700926 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
927 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800928 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700929 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700930 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700931 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800932 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700933 sys.exit(2)
934
Doug Zongkereef39442009-04-02 12:14:19 -0700935 for o, a in opts:
936 if o in ("-h", "--help"):
937 Usage(docstring)
938 sys.exit()
939 elif o in ("-v", "--verbose"):
940 OPTIONS.verbose = True
941 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700942 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700943 elif o in ("--signapk_path",):
944 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800945 elif o in ("--signapk_shared_library_path",):
946 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700947 elif o in ("--extra_signapk_args",):
948 OPTIONS.extra_signapk_args = shlex.split(a)
949 elif o in ("--java_path",):
950 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700951 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800952 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700953 elif o in ("--public_key_suffix",):
954 OPTIONS.public_key_suffix = a
955 elif o in ("--private_key_suffix",):
956 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800957 elif o in ("--boot_signer_path",):
958 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700959 elif o in ("--boot_signer_args",):
960 OPTIONS.boot_signer_args = shlex.split(a)
961 elif o in ("--verity_signer_path",):
962 OPTIONS.verity_signer_path = a
963 elif o in ("--verity_signer_args",):
964 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700965 elif o in ("-s", "--device_specific"):
966 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800967 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800968 key, value = a.split("=", 1)
969 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700970 else:
971 if extra_option_handler is None or not extra_option_handler(o, a):
972 assert False, "unknown option \"%s\"" % (o,)
973
Doug Zongker85448772014-09-09 14:59:20 -0700974 if OPTIONS.search_path:
975 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
976 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700977
978 return args
979
980
Tao Bao4c851b12016-09-19 13:54:38 -0700981def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700982 """Make a temp file and add it to the list of things to be deleted
983 when Cleanup() is called. Return the filename."""
984 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
985 os.close(fd)
986 OPTIONS.tempfiles.append(fn)
987 return fn
988
989
Tao Bao1c830bf2017-12-25 10:43:47 -0800990def MakeTempDir(prefix='tmp', suffix=''):
991 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
992
993 Returns:
994 The absolute pathname of the new directory.
995 """
996 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
997 OPTIONS.tempfiles.append(dir_name)
998 return dir_name
999
1000
Doug Zongkereef39442009-04-02 12:14:19 -07001001def Cleanup():
1002 for i in OPTIONS.tempfiles:
1003 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001004 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001005 else:
1006 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001007 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001008
1009
1010class PasswordManager(object):
1011 def __init__(self):
1012 self.editor = os.getenv("EDITOR", None)
1013 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
1014
1015 def GetPasswords(self, items):
1016 """Get passwords corresponding to each string in 'items',
1017 returning a dict. (The dict may have keys in addition to the
1018 values in 'items'.)
1019
1020 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1021 user edit that file to add more needed passwords. If no editor is
1022 available, or $ANDROID_PW_FILE isn't define, prompts the user
1023 interactively in the ordinary way.
1024 """
1025
1026 current = self.ReadFile()
1027
1028 first = True
1029 while True:
1030 missing = []
1031 for i in items:
1032 if i not in current or not current[i]:
1033 missing.append(i)
1034 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001035 if not missing:
1036 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001037
1038 for i in missing:
1039 current[i] = ""
1040
1041 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001042 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001043 answer = raw_input("try to edit again? [y]> ").strip()
1044 if answer and answer[0] not in 'yY':
1045 raise RuntimeError("key passwords unavailable")
1046 first = False
1047
1048 current = self.UpdateAndReadFile(current)
1049
Dan Albert8b72aef2015-03-23 19:13:21 -07001050 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001051 """Prompt the user to enter a value (password) for each key in
1052 'current' whose value is fales. Returns a new dict with all the
1053 values.
1054 """
1055 result = {}
1056 for k, v in sorted(current.iteritems()):
1057 if v:
1058 result[k] = v
1059 else:
1060 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001061 result[k] = getpass.getpass(
1062 "Enter password for %s key> " % k).strip()
1063 if result[k]:
1064 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001065 return result
1066
1067 def UpdateAndReadFile(self, current):
1068 if not self.editor or not self.pwfile:
1069 return self.PromptResult(current)
1070
1071 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001072 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001073 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1074 f.write("# (Additional spaces are harmless.)\n\n")
1075
1076 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001077 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1078 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001079 f.write("[[[ %s ]]] %s\n" % (v, k))
1080 if not v and first_line is None:
1081 # position cursor on first line with no password.
1082 first_line = i + 4
1083 f.close()
1084
1085 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1086 _, _ = p.communicate()
1087
1088 return self.ReadFile()
1089
1090 def ReadFile(self):
1091 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001092 if self.pwfile is None:
1093 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001094 try:
1095 f = open(self.pwfile, "r")
1096 for line in f:
1097 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001098 if not line or line[0] == '#':
1099 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001100 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1101 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001102 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001103 else:
1104 result[m.group(2)] = m.group(1)
1105 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001106 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001107 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001108 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001109 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001110
1111
Dan Albert8e0178d2015-01-27 15:53:15 -08001112def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1113 compress_type=None):
1114 import datetime
1115
1116 # http://b/18015246
1117 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1118 # for files larger than 2GiB. We can work around this by adjusting their
1119 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1120 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1121 # it isn't clear to me exactly what circumstances cause this).
1122 # `zipfile.write()` must be used directly to work around this.
1123 #
1124 # This mess can be avoided if we port to python3.
1125 saved_zip64_limit = zipfile.ZIP64_LIMIT
1126 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1127
1128 if compress_type is None:
1129 compress_type = zip_file.compression
1130 if arcname is None:
1131 arcname = filename
1132
1133 saved_stat = os.stat(filename)
1134
1135 try:
1136 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1137 # file to be zipped and reset it when we're done.
1138 os.chmod(filename, perms)
1139
1140 # Use a fixed timestamp so the output is repeatable.
1141 epoch = datetime.datetime.fromtimestamp(0)
1142 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1143 os.utime(filename, (timestamp, timestamp))
1144
1145 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1146 finally:
1147 os.chmod(filename, saved_stat.st_mode)
1148 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1149 zipfile.ZIP64_LIMIT = saved_zip64_limit
1150
1151
Tao Bao58c1b962015-05-20 09:32:18 -07001152def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001153 compress_type=None):
1154 """Wrap zipfile.writestr() function to work around the zip64 limit.
1155
1156 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1157 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1158 when calling crc32(bytes).
1159
1160 But it still works fine to write a shorter string into a large zip file.
1161 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1162 when we know the string won't be too long.
1163 """
1164
1165 saved_zip64_limit = zipfile.ZIP64_LIMIT
1166 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1167
1168 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1169 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001170 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001171 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001172 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001173 else:
Tao Baof3282b42015-04-01 11:21:55 -07001174 zinfo = zinfo_or_arcname
1175
1176 # If compress_type is given, it overrides the value in zinfo.
1177 if compress_type is not None:
1178 zinfo.compress_type = compress_type
1179
Tao Bao58c1b962015-05-20 09:32:18 -07001180 # If perms is given, it has a priority.
1181 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001182 # If perms doesn't set the file type, mark it as a regular file.
1183 if perms & 0o770000 == 0:
1184 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001185 zinfo.external_attr = perms << 16
1186
Tao Baof3282b42015-04-01 11:21:55 -07001187 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001188 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1189
Dan Albert8b72aef2015-03-23 19:13:21 -07001190 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001191 zipfile.ZIP64_LIMIT = saved_zip64_limit
1192
1193
Tao Bao89d7ab22017-12-14 17:05:33 -08001194def ZipDelete(zip_filename, entries):
1195 """Deletes entries from a ZIP file.
1196
1197 Since deleting entries from a ZIP file is not supported, it shells out to
1198 'zip -d'.
1199
1200 Args:
1201 zip_filename: The name of the ZIP file.
1202 entries: The name of the entry, or the list of names to be deleted.
1203
1204 Raises:
1205 AssertionError: In case of non-zero return from 'zip'.
1206 """
1207 if isinstance(entries, basestring):
1208 entries = [entries]
1209 cmd = ["zip", "-d", zip_filename] + entries
1210 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1211 stdoutdata, _ = proc.communicate()
1212 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1213 stdoutdata)
1214
1215
Tao Baof3282b42015-04-01 11:21:55 -07001216def ZipClose(zip_file):
1217 # http://b/18015246
1218 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1219 # central directory.
1220 saved_zip64_limit = zipfile.ZIP64_LIMIT
1221 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1222
1223 zip_file.close()
1224
1225 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001226
1227
1228class DeviceSpecificParams(object):
1229 module = None
1230 def __init__(self, **kwargs):
1231 """Keyword arguments to the constructor become attributes of this
1232 object, which is passed to all functions in the device-specific
1233 module."""
1234 for k, v in kwargs.iteritems():
1235 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001236 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001237
1238 if self.module is None:
1239 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001240 if not path:
1241 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001242 try:
1243 if os.path.isdir(path):
1244 info = imp.find_module("releasetools", [path])
1245 else:
1246 d, f = os.path.split(path)
1247 b, x = os.path.splitext(f)
1248 if x == ".py":
1249 f = b
1250 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001251 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001252 self.module = imp.load_module("device_specific", *info)
1253 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001254 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001255
1256 def _DoCall(self, function_name, *args, **kwargs):
1257 """Call the named function in the device-specific module, passing
1258 the given args and kwargs. The first argument to the call will be
1259 the DeviceSpecific object itself. If there is no module, or the
1260 module does not define the function, return the value of the
1261 'default' kwarg (which itself defaults to None)."""
1262 if self.module is None or not hasattr(self.module, function_name):
1263 return kwargs.get("default", None)
1264 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1265
1266 def FullOTA_Assertions(self):
1267 """Called after emitting the block of assertions at the top of a
1268 full OTA package. Implementations can add whatever additional
1269 assertions they like."""
1270 return self._DoCall("FullOTA_Assertions")
1271
Doug Zongkere5ff5902012-01-17 10:55:37 -08001272 def FullOTA_InstallBegin(self):
1273 """Called at the start of full OTA installation."""
1274 return self._DoCall("FullOTA_InstallBegin")
1275
Doug Zongker05d3dea2009-06-22 11:32:31 -07001276 def FullOTA_InstallEnd(self):
1277 """Called at the end of full OTA installation; typically this is
1278 used to install the image for the device's baseband processor."""
1279 return self._DoCall("FullOTA_InstallEnd")
1280
1281 def IncrementalOTA_Assertions(self):
1282 """Called after emitting the block of assertions at the top of an
1283 incremental OTA package. Implementations can add whatever
1284 additional assertions they like."""
1285 return self._DoCall("IncrementalOTA_Assertions")
1286
Doug Zongkere5ff5902012-01-17 10:55:37 -08001287 def IncrementalOTA_VerifyBegin(self):
1288 """Called at the start of the verification phase of incremental
1289 OTA installation; additional checks can be placed here to abort
1290 the script before any changes are made."""
1291 return self._DoCall("IncrementalOTA_VerifyBegin")
1292
Doug Zongker05d3dea2009-06-22 11:32:31 -07001293 def IncrementalOTA_VerifyEnd(self):
1294 """Called at the end of the verification phase of incremental OTA
1295 installation; additional checks can be placed here to abort the
1296 script before any changes are made."""
1297 return self._DoCall("IncrementalOTA_VerifyEnd")
1298
Doug Zongkere5ff5902012-01-17 10:55:37 -08001299 def IncrementalOTA_InstallBegin(self):
1300 """Called at the start of incremental OTA installation (after
1301 verification is complete)."""
1302 return self._DoCall("IncrementalOTA_InstallBegin")
1303
Doug Zongker05d3dea2009-06-22 11:32:31 -07001304 def IncrementalOTA_InstallEnd(self):
1305 """Called at the end of incremental OTA installation; typically
1306 this is used to install the image for the device's baseband
1307 processor."""
1308 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001309
Tao Bao9bc6bb22015-11-09 16:58:28 -08001310 def VerifyOTA_Assertions(self):
1311 return self._DoCall("VerifyOTA_Assertions")
1312
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001313class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001314 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001315 self.name = name
1316 self.data = data
1317 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001318 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001319 self.sha1 = sha1(data).hexdigest()
1320
1321 @classmethod
1322 def FromLocalFile(cls, name, diskname):
1323 f = open(diskname, "rb")
1324 data = f.read()
1325 f.close()
1326 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001327
1328 def WriteToTemp(self):
1329 t = tempfile.NamedTemporaryFile()
1330 t.write(self.data)
1331 t.flush()
1332 return t
1333
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001334 def WriteToDir(self, d):
1335 with open(os.path.join(d, self.name), "wb") as fp:
1336 fp.write(self.data)
1337
Geremy Condra36bd3652014-02-06 19:45:10 -08001338 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001339 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001340
1341DIFF_PROGRAM_BY_EXT = {
1342 ".gz" : "imgdiff",
1343 ".zip" : ["imgdiff", "-z"],
1344 ".jar" : ["imgdiff", "-z"],
1345 ".apk" : ["imgdiff", "-z"],
1346 ".img" : "imgdiff",
1347 }
1348
1349class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001350 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001351 self.tf = tf
1352 self.sf = sf
1353 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001354 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001355
1356 def ComputePatch(self):
1357 """Compute the patch (as a string of data) needed to turn sf into
1358 tf. Returns the same tuple as GetPatch()."""
1359
1360 tf = self.tf
1361 sf = self.sf
1362
Doug Zongker24cd2802012-08-14 16:36:15 -07001363 if self.diff_program:
1364 diff_program = self.diff_program
1365 else:
1366 ext = os.path.splitext(tf.name)[1]
1367 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001368
1369 ttemp = tf.WriteToTemp()
1370 stemp = sf.WriteToTemp()
1371
1372 ext = os.path.splitext(tf.name)[1]
1373
1374 try:
1375 ptemp = tempfile.NamedTemporaryFile()
1376 if isinstance(diff_program, list):
1377 cmd = copy.copy(diff_program)
1378 else:
1379 cmd = [diff_program]
1380 cmd.append(stemp.name)
1381 cmd.append(ttemp.name)
1382 cmd.append(ptemp.name)
1383 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001384 err = []
1385 def run():
1386 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001387 if e:
1388 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001389 th = threading.Thread(target=run)
1390 th.start()
1391 th.join(timeout=300) # 5 mins
1392 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001393 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001394 p.terminate()
1395 th.join(5)
1396 if th.is_alive():
1397 p.kill()
1398 th.join()
1399
Tianjie Xua2a9f992018-01-05 15:15:54 -08001400 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001401 print("WARNING: failure running %s:\n%s\n" % (
1402 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001403 self.patch = None
1404 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001405 diff = ptemp.read()
1406 finally:
1407 ptemp.close()
1408 stemp.close()
1409 ttemp.close()
1410
1411 self.patch = diff
1412 return self.tf, self.sf, self.patch
1413
1414
1415 def GetPatch(self):
1416 """Return a tuple (target_file, source_file, patch_data).
1417 patch_data may be None if ComputePatch hasn't been called, or if
1418 computing the patch failed."""
1419 return self.tf, self.sf, self.patch
1420
1421
1422def ComputeDifferences(diffs):
1423 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001424 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001425
1426 # Do the largest files first, to try and reduce the long-pole effect.
1427 by_size = [(i.tf.size, i) for i in diffs]
1428 by_size.sort(reverse=True)
1429 by_size = [i[1] for i in by_size]
1430
1431 lock = threading.Lock()
1432 diff_iter = iter(by_size) # accessed under lock
1433
1434 def worker():
1435 try:
1436 lock.acquire()
1437 for d in diff_iter:
1438 lock.release()
1439 start = time.time()
1440 d.ComputePatch()
1441 dur = time.time() - start
1442 lock.acquire()
1443
1444 tf, sf, patch = d.GetPatch()
1445 if sf.name == tf.name:
1446 name = tf.name
1447 else:
1448 name = "%s (%s)" % (tf.name, sf.name)
1449 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001450 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001451 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001452 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1453 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001454 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001455 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001456 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001457 raise
1458
1459 # start worker threads; wait for them all to finish.
1460 threads = [threading.Thread(target=worker)
1461 for i in range(OPTIONS.worker_threads)]
1462 for th in threads:
1463 th.start()
1464 while threads:
1465 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001466
1467
Dan Albert8b72aef2015-03-23 19:13:21 -07001468class BlockDifference(object):
1469 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001470 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001471 self.tgt = tgt
1472 self.src = src
1473 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001474 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001475 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001476
Tao Baodd2a5892015-03-12 12:32:37 -07001477 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001478 version = max(
1479 int(i) for i in
1480 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001481 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001482 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001483
1484 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001485 version=self.version,
1486 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001487 tmpdir = tempfile.mkdtemp()
1488 OPTIONS.tempfiles.append(tmpdir)
1489 self.path = os.path.join(tmpdir, partition)
1490 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001491 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001492 self.touched_src_ranges = b.touched_src_ranges
1493 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001494
Tao Baoaac4ad52015-10-16 15:26:34 -07001495 if src is None:
1496 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1497 else:
1498 _, self.device = GetTypeAndDevice("/" + partition,
1499 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001500
Tao Baod8d14be2016-02-04 14:26:02 -08001501 @property
1502 def required_cache(self):
1503 return self._required_cache
1504
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001505 def WriteScript(self, script, output_zip, progress=None):
1506 if not self.src:
1507 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001508 script.Print("Patching %s image unconditionally..." % (self.partition,))
1509 else:
1510 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001511
Dan Albert8b72aef2015-03-23 19:13:21 -07001512 if progress:
1513 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001514 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001515 if OPTIONS.verify:
1516 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001517
Tao Bao9bc6bb22015-11-09 16:58:28 -08001518 def WriteStrictVerifyScript(self, script):
1519 """Verify all the blocks in the care_map, including clobbered blocks.
1520
1521 This differs from the WriteVerifyScript() function: a) it prints different
1522 error messages; b) it doesn't allow half-way updated images to pass the
1523 verification."""
1524
1525 partition = self.partition
1526 script.Print("Verifying %s..." % (partition,))
1527 ranges = self.tgt.care_map
1528 ranges_str = ranges.to_string_raw()
1529 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1530 'ui_print(" Verified.") || '
1531 'ui_print("\\"%s\\" has unexpected contents.");' % (
1532 self.device, ranges_str,
1533 self.tgt.TotalSha1(include_clobbered_blocks=True),
1534 self.device))
1535 script.AppendExtra("")
1536
Tao Baod522bdc2016-04-12 15:53:16 -07001537 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001538 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001539
1540 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001541 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001542 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001543
1544 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001545 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001546 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001547 ranges = self.touched_src_ranges
1548 expected_sha1 = self.touched_src_sha1
1549 else:
1550 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1551 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001552
1553 # No blocks to be checked, skipping.
1554 if not ranges:
1555 return
1556
Tao Bao5ece99d2015-05-12 11:42:31 -07001557 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001558 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1559 'block_image_verify("%s", '
1560 'package_extract_file("%s.transfer.list"), '
1561 '"%s.new.dat", "%s.patch.dat")) then') % (
1562 self.device, ranges_str, expected_sha1,
1563 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001564 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001565 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001566
Tianjie Xufc3422a2015-12-15 11:53:59 -08001567 if self.version >= 4:
1568
1569 # Bug: 21124327
1570 # When generating incrementals for the system and vendor partitions in
1571 # version 4 or newer, explicitly check the first block (which contains
1572 # the superblock) of the partition to see if it's what we expect. If
1573 # this check fails, give an explicit log message about the partition
1574 # having been remounted R/W (the most likely explanation).
1575 if self.check_first_block:
1576 script.AppendExtra('check_first_block("%s");' % (self.device,))
1577
1578 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001579 if partition == "system":
1580 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1581 else:
1582 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001583 script.AppendExtra((
1584 'ifelse (block_image_recover("{device}", "{ranges}") && '
1585 'block_image_verify("{device}", '
1586 'package_extract_file("{partition}.transfer.list"), '
1587 '"{partition}.new.dat", "{partition}.patch.dat"), '
1588 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001589 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001590 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001591 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001592
Tao Baodd2a5892015-03-12 12:32:37 -07001593 # Abort the OTA update. Note that the incremental OTA cannot be applied
1594 # even if it may match the checksum of the target partition.
1595 # a) If version < 3, operations like move and erase will make changes
1596 # unconditionally and damage the partition.
1597 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001598 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001599 if partition == "system":
1600 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1601 else:
1602 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1603 script.AppendExtra((
1604 'abort("E%d: %s partition has unexpected contents");\n'
1605 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001606
Tao Bao5fcaaef2015-06-01 13:40:49 -07001607 def _WritePostInstallVerifyScript(self, script):
1608 partition = self.partition
1609 script.Print('Verifying the updated %s image...' % (partition,))
1610 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1611 ranges = self.tgt.care_map
1612 ranges_str = ranges.to_string_raw()
1613 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1614 self.device, ranges_str,
1615 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001616
1617 # Bug: 20881595
1618 # Verify that extended blocks are really zeroed out.
1619 if self.tgt.extended:
1620 ranges_str = self.tgt.extended.to_string_raw()
1621 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1622 self.device, ranges_str,
1623 self._HashZeroBlocks(self.tgt.extended.size())))
1624 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001625 if partition == "system":
1626 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1627 else:
1628 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001629 script.AppendExtra(
1630 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001631 ' abort("E%d: %s partition has unexpected non-zero contents after '
1632 'OTA update");\n'
1633 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001634 else:
1635 script.Print('Verified the updated %s image.' % (partition,))
1636
Tianjie Xu209db462016-05-24 17:34:52 -07001637 if partition == "system":
1638 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1639 else:
1640 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1641
Tao Bao5fcaaef2015-06-01 13:40:49 -07001642 script.AppendExtra(
1643 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001644 ' abort("E%d: %s partition has unexpected contents after OTA '
1645 'update");\n'
1646 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001647
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001648 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001649 ZipWrite(output_zip,
1650 '{}.transfer.list'.format(self.path),
1651 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001652
1653 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1654 # almost triples the compression time but doesn't further reduce the size too much.
1655 # For a typical 1.8G system.new.dat
1656 # zip | brotli(quality 6) | brotli(quality 9)
1657 # compressed_size: 942M | 869M (~8% reduced) | 854M
1658 # compression_time: 75s | 265s | 719s
1659 # decompression_time: 15s | 25s | 25s
1660
1661 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001662 brotli_cmd = ['brotli', '--quality=6',
1663 '--output={}.new.dat.br'.format(self.path),
1664 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001665 print("Compressing {}.new.dat with brotli".format(self.partition))
Alex Deymob10e07a2017-11-09 23:53:42 +01001666 p = Run(brotli_cmd, stdout=subprocess.PIPE)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001667 p.communicate()
1668 assert p.returncode == 0,\
1669 'compression of {}.new.dat failed'.format(self.partition)
1670
1671 new_data_name = '{}.new.dat.br'.format(self.partition)
1672 ZipWrite(output_zip,
1673 '{}.new.dat.br'.format(self.path),
1674 new_data_name,
1675 compress_type=zipfile.ZIP_STORED)
1676 else:
1677 new_data_name = '{}.new.dat'.format(self.partition)
1678 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1679
Dan Albert8e0178d2015-01-27 15:53:15 -08001680 ZipWrite(output_zip,
1681 '{}.patch.dat'.format(self.path),
1682 '{}.patch.dat'.format(self.partition),
1683 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001684
Tianjie Xu209db462016-05-24 17:34:52 -07001685 if self.partition == "system":
1686 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1687 else:
1688 code = ErrorCode.VENDOR_UPDATE_FAILURE
1689
Dan Albert8e0178d2015-01-27 15:53:15 -08001690 call = ('block_image_update("{device}", '
1691 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001692 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001693 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001694 device=self.device, partition=self.partition,
1695 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001696 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001697
Dan Albert8b72aef2015-03-23 19:13:21 -07001698 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001699 data = source.ReadRangeSet(ranges)
1700 ctx = sha1()
1701
1702 for p in data:
1703 ctx.update(p)
1704
1705 return ctx.hexdigest()
1706
Tao Baoe9b61912015-07-09 17:37:49 -07001707 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1708 """Return the hash value for all zero blocks."""
1709 zero_block = '\x00' * 4096
1710 ctx = sha1()
1711 for _ in range(num_blocks):
1712 ctx.update(zero_block)
1713
1714 return ctx.hexdigest()
1715
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001716
1717DataImage = blockimgdiff.DataImage
1718
Doug Zongker96a57e72010-09-26 14:57:41 -07001719# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001720PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001721 "ext4": "EMMC",
1722 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001723 "f2fs": "EMMC",
1724 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001725}
Doug Zongker96a57e72010-09-26 14:57:41 -07001726
1727def GetTypeAndDevice(mount_point, info):
1728 fstab = info["fstab"]
1729 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001730 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1731 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001732 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001733 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001734
1735
1736def ParseCertificate(data):
1737 """Parse a PEM-format certificate."""
1738 cert = []
1739 save = False
1740 for line in data.split("\n"):
1741 if "--END CERTIFICATE--" in line:
1742 break
1743 if save:
1744 cert.append(line)
1745 if "--BEGIN CERTIFICATE--" in line:
1746 save = True
1747 cert = "".join(cert).decode('base64')
1748 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001749
Doug Zongker412c02f2014-02-13 10:58:24 -08001750def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1751 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001752 """Generate a binary patch that creates the recovery image starting
1753 with the boot image. (Most of the space in these images is just the
1754 kernel, which is identical for the two, so the resulting patch
1755 should be efficient.) Add it to the output zip, along with a shell
1756 script that is run from init.rc on first boot to actually do the
1757 patching and install the new recovery image.
1758
1759 recovery_img and boot_img should be File objects for the
1760 corresponding images. info should be the dictionary returned by
1761 common.LoadInfoDict() on the input target_files.
1762 """
1763
Doug Zongker412c02f2014-02-13 10:58:24 -08001764 if info_dict is None:
1765 info_dict = OPTIONS.info_dict
1766
Tao Baof2cffbd2015-07-22 12:33:18 -07001767 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001768
Tao Baof2cffbd2015-07-22 12:33:18 -07001769 if full_recovery_image:
1770 output_sink("etc/recovery.img", recovery_img.data)
1771
1772 else:
1773 diff_program = ["imgdiff"]
1774 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1775 if os.path.exists(path):
1776 diff_program.append("-b")
1777 diff_program.append(path)
1778 bonus_args = "-b /system/etc/recovery-resource.dat"
1779 else:
1780 bonus_args = ""
1781
1782 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1783 _, _, patch = d.ComputePatch()
1784 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001785
Dan Albertebb19aa2015-03-27 19:11:53 -07001786 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001787 # The following GetTypeAndDevice()s need to use the path in the target
1788 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001789 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1790 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1791 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001792 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001793
Tao Baof2cffbd2015-07-22 12:33:18 -07001794 if full_recovery_image:
1795 sh = """#!/system/bin/sh
1796if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1797 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"
1798else
1799 log -t recovery "Recovery image already installed"
1800fi
1801""" % {'type': recovery_type,
1802 'device': recovery_device,
1803 'sha1': recovery_img.sha1,
1804 'size': recovery_img.size}
1805 else:
1806 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001807if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1808 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"
1809else
1810 log -t recovery "Recovery image already installed"
1811fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001812""" % {'boot_size': boot_img.size,
1813 'boot_sha1': boot_img.sha1,
1814 'recovery_size': recovery_img.size,
1815 'recovery_sha1': recovery_img.sha1,
1816 'boot_type': boot_type,
1817 'boot_device': boot_device,
1818 'recovery_type': recovery_type,
1819 'recovery_device': recovery_device,
1820 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001821
1822 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001823 # in the L release.
1824 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001825
Tao Bao89fbb0f2017-01-10 10:47:58 -08001826 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001827
1828 output_sink(sh_location, sh)