blob: aff3d62c123e5e56faefe4588f9543c333518d98 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Doug Zongkerea5d7a92010-09-12 15:26:16 -070017import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070018import errno
Doug Zongkereef39442009-04-02 12:14:19 -070019import getopt
20import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010021import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070022import imp
Doug Zongkereef39442009-04-02 12:14:19 -070023import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080024import platform
Doug Zongkereef39442009-04-02 12:14:19 -070025import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070026import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070027import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080028import string
Doug Zongkereef39442009-04-02 12:14:19 -070029import subprocess
30import sys
31import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070032import threading
33import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070034import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080035from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070036
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070037import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080038import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070039
Dan Albert8b72aef2015-03-23 19:13:21 -070040class Options(object):
41 def __init__(self):
42 platform_search_path = {
43 "linux2": "out/host/linux-x86",
44 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070045 }
Doug Zongker85448772014-09-09 14:59:20 -070046
Tao Bao76def242017-11-21 09:25:31 -080047 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080049 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070050 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080052 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070053 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070055 # use otatools built boot_signer by default
56 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070057 self.boot_signer_args = []
58 self.verity_signer_path = None
59 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.verbose = False
61 self.tempfiles = []
62 self.device_specific = None
63 self.extras = {}
64 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070065 self.source_info_dict = None
66 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070068 # Stash size cannot exceed cache_size * threshold.
69 self.cache_size = None
70 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070071
72
73OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070074
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080075
76# Values for "certificate" in apkcerts that mean special things.
77SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
78
Tao Bao9dd909e2017-11-14 11:27:32 -080079
80# The partitions allowed to be signed by AVB (Android verified boot 2.0).
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
Tao Bao76def242017-11-21 09:25:31 -0800239 system_root_image = d.get("system_root_image") == "true"
240 if d.get("no_recovery") != "true":
Tianjie Xucfa86222016-03-07 16:31:19 -0800241 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800242 d["fstab"] = LoadRecoveryFSTab(
243 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
244 elif d.get("recovery_as_boot") == "true":
Tianjie Xucfa86222016-03-07 16:31:19 -0800245 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800246 d["fstab"] = LoadRecoveryFSTab(
247 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tianjie Xucfa86222016-03-07 16:31:19 -0800248 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
Tao Bao76def242017-11-21 09:25:31 -0800443 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700444 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
Tao Bao76def242017-11-21 09:25:31 -0800447 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000448 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
Tao Bao76def242017-11-21 09:25:31 -0800455 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700456 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
Tao Bao76def242017-11-21 09:25:31 -0800473 if (info_dict.get("boot_signer") == "true" and
474 info_dict.get("verity_key")):
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.
Tao Bao76def242017-11-21 09:25:31 -0800491 elif info_dict.get("vboot"):
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):
Tao Bao76def242017-11-21 09:25:31 -0800580 """Gunzips the given gzip compressed file to a given output file."""
581 with gzip.open(in_filename, "rb") as in_file, \
582 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100583 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))
Tao Bao76def242017-11-21 09:25:31 -0800736 key_passwords.update(dict.fromkeys(no_passwords))
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,
Tao Bao76def242017-11-21 09:25:31 -0800801 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700802 """Sign the input_name zip/jar/apk, producing output_name. Use the
803 given key and password (the latter may be None if the key does not
804 have a password.
805
Doug Zongker951495f2009-08-14 12:44:19 -0700806 If whole_file is true, use the "-w" option to SignApk to embed a
807 signature that covers the whole file in the archive comment of the
808 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800809
810 min_api_level is the API Level (int) of the oldest platform this file may end
811 up on. If not specified for an APK, the API Level is obtained by interpreting
812 the minSdkVersion attribute of the APK's AndroidManifest.xml.
813
814 codename_to_api_level_map is needed to translate the codename which may be
815 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700816 """
Tao Bao76def242017-11-21 09:25:31 -0800817 if codename_to_api_level_map is None:
818 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700819
Alex Klyubin9667b182015-12-10 13:38:50 -0800820 java_library_path = os.path.join(
821 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
822
Tao Baoe95540e2016-11-08 12:08:53 -0800823 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
824 ["-Djava.library.path=" + java_library_path,
825 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
826 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700827 if whole_file:
828 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800829
830 min_sdk_version = min_api_level
831 if min_sdk_version is None:
832 if not whole_file:
833 min_sdk_version = GetMinSdkVersionInt(
834 input_name, codename_to_api_level_map)
835 if min_sdk_version is not None:
836 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
837
T.R. Fullhart37e10522013-03-18 10:31:26 -0700838 cmd.extend([key + OPTIONS.public_key_suffix,
839 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800840 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700841
Tao Bao80921982018-03-21 21:02:19 -0700842 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
843 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700844 if password is not None:
845 password += "\n"
Tao Bao80921982018-03-21 21:02:19 -0700846 stdoutdata, _ = p.communicate(password)
Doug Zongkereef39442009-04-02 12:14:19 -0700847 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700848 raise ExternalError(
849 "Failed to run signapk.jar: return code {}:\n{}".format(
850 p.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700851
Doug Zongkereef39442009-04-02 12:14:19 -0700852
Doug Zongker37974732010-09-16 17:44:38 -0700853def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800854 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700855
Tao Bao9dd909e2017-11-14 11:27:32 -0800856 For non-AVB images, raise exception if the data is too big. Print a warning
857 if the data is nearing the maximum size.
858
859 For AVB images, the actual image size should be identical to the limit.
860
861 Args:
862 data: A string that contains all the data for the partition.
863 target: The partition name. The ".img" suffix is optional.
864 info_dict: The dict to be looked up for relevant info.
865 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700866 if target.endswith(".img"):
867 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700868 mount_point = "/" + target
869
Ying Wangf8824af2014-06-03 14:07:27 -0700870 fs_type = None
871 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700872 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700873 if mount_point == "/userdata":
874 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700875 p = info_dict["fstab"][mount_point]
876 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800877 device = p.device
878 if "/" in device:
879 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -0800880 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -0700881 if not fs_type or not limit:
882 return
Doug Zongkereef39442009-04-02 12:14:19 -0700883
Andrew Boie0f9aec82012-02-14 09:32:52 -0800884 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800885 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
886 # path.
887 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
888 if size != limit:
889 raise ExternalError(
890 "Mismatching image size for %s: expected %d actual %d" % (
891 target, limit, size))
892 else:
893 pct = float(size) * 100.0 / limit
894 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
895 if pct >= 99.0:
896 raise ExternalError(msg)
897 elif pct >= 95.0:
898 print("\n WARNING: %s\n" % (msg,))
899 elif OPTIONS.verbose:
900 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700901
902
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800903def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800904 """Parses the APK certs info from a given target-files zip.
905
906 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
907 tuple with the following elements: (1) a dictionary that maps packages to
908 certs (based on the "certificate" and "private_key" attributes in the file;
909 (2) a string representing the extension of compressed APKs in the target files
910 (e.g ".gz", ".bro").
911
912 Args:
913 tf_zip: The input target_files ZipFile (already open).
914
915 Returns:
916 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
917 the extension string of compressed APKs (e.g. ".gz"), or None if there's
918 no compressed APKs.
919 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800920 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100921 compressed_extension = None
922
Tao Bao0f990332017-09-08 19:02:54 -0700923 # META/apkcerts.txt contains the info for _all_ the packages known at build
924 # time. Filter out the ones that are not installed.
925 installed_files = set()
926 for name in tf_zip.namelist():
927 basename = os.path.basename(name)
928 if basename:
929 installed_files.add(basename)
930
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800931 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
932 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700933 if not line:
934 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800935 m = re.match(
936 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
937 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
938 line)
939 if not m:
940 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100941
Tao Bao818ddf52018-01-05 11:17:34 -0800942 matches = m.groupdict()
943 cert = matches["CERT"]
944 privkey = matches["PRIVKEY"]
945 name = matches["NAME"]
946 this_compressed_extension = matches["COMPRESSED"]
947
948 public_key_suffix_len = len(OPTIONS.public_key_suffix)
949 private_key_suffix_len = len(OPTIONS.private_key_suffix)
950 if cert in SPECIAL_CERT_STRINGS and not privkey:
951 certmap[name] = cert
952 elif (cert.endswith(OPTIONS.public_key_suffix) and
953 privkey.endswith(OPTIONS.private_key_suffix) and
954 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
955 certmap[name] = cert[:-public_key_suffix_len]
956 else:
957 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
958
959 if not this_compressed_extension:
960 continue
961
962 # Only count the installed files.
963 filename = name + '.' + this_compressed_extension
964 if filename not in installed_files:
965 continue
966
967 # Make sure that all the values in the compression map have the same
968 # extension. We don't support multiple compression methods in the same
969 # system image.
970 if compressed_extension:
971 if this_compressed_extension != compressed_extension:
972 raise ValueError(
973 "Multiple compressed extensions: {} vs {}".format(
974 compressed_extension, this_compressed_extension))
975 else:
976 compressed_extension = this_compressed_extension
977
978 return (certmap,
979 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800980
981
Doug Zongkereef39442009-04-02 12:14:19 -0700982COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -0700983Global options
984
985 -p (--path) <dir>
986 Prepend <dir>/bin to the list of places to search for binaries run by this
987 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700988
Doug Zongker05d3dea2009-06-22 11:32:31 -0700989 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -0700990 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -0700991
Tao Bao30df8b42018-04-23 15:32:53 -0700992 -x (--extra) <key=value>
993 Add a key/value pair to the 'extras' dict, which device-specific extension
994 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -0800995
Doug Zongkereef39442009-04-02 12:14:19 -0700996 -v (--verbose)
997 Show command lines being executed.
998
999 -h (--help)
1000 Display this usage message and exit.
1001"""
1002
1003def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001004 print(docstring.rstrip("\n"))
1005 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001006
1007
1008def ParseOptions(argv,
1009 docstring,
1010 extra_opts="", extra_long_opts=(),
1011 extra_option_handler=None):
1012 """Parse the options in argv and return any arguments that aren't
1013 flags. docstring is the calling module's docstring, to be displayed
1014 for errors and -h. extra_opts and extra_long_opts are for flags
1015 defined by the caller, which are processed by passing them to
1016 extra_option_handler."""
1017
1018 try:
1019 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001020 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001021 ["help", "verbose", "path=", "signapk_path=",
1022 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001023 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001024 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1025 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001026 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001027 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001028 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001029 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001030 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001031 sys.exit(2)
1032
Doug Zongkereef39442009-04-02 12:14:19 -07001033 for o, a in opts:
1034 if o in ("-h", "--help"):
1035 Usage(docstring)
1036 sys.exit()
1037 elif o in ("-v", "--verbose"):
1038 OPTIONS.verbose = True
1039 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001040 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001041 elif o in ("--signapk_path",):
1042 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001043 elif o in ("--signapk_shared_library_path",):
1044 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001045 elif o in ("--extra_signapk_args",):
1046 OPTIONS.extra_signapk_args = shlex.split(a)
1047 elif o in ("--java_path",):
1048 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001049 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001050 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001051 elif o in ("--public_key_suffix",):
1052 OPTIONS.public_key_suffix = a
1053 elif o in ("--private_key_suffix",):
1054 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001055 elif o in ("--boot_signer_path",):
1056 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001057 elif o in ("--boot_signer_args",):
1058 OPTIONS.boot_signer_args = shlex.split(a)
1059 elif o in ("--verity_signer_path",):
1060 OPTIONS.verity_signer_path = a
1061 elif o in ("--verity_signer_args",):
1062 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001063 elif o in ("-s", "--device_specific"):
1064 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001065 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001066 key, value = a.split("=", 1)
1067 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001068 else:
1069 if extra_option_handler is None or not extra_option_handler(o, a):
1070 assert False, "unknown option \"%s\"" % (o,)
1071
Doug Zongker85448772014-09-09 14:59:20 -07001072 if OPTIONS.search_path:
1073 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1074 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001075
1076 return args
1077
1078
Tao Bao4c851b12016-09-19 13:54:38 -07001079def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001080 """Make a temp file and add it to the list of things to be deleted
1081 when Cleanup() is called. Return the filename."""
1082 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1083 os.close(fd)
1084 OPTIONS.tempfiles.append(fn)
1085 return fn
1086
1087
Tao Bao1c830bf2017-12-25 10:43:47 -08001088def MakeTempDir(prefix='tmp', suffix=''):
1089 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1090
1091 Returns:
1092 The absolute pathname of the new directory.
1093 """
1094 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1095 OPTIONS.tempfiles.append(dir_name)
1096 return dir_name
1097
1098
Doug Zongkereef39442009-04-02 12:14:19 -07001099def Cleanup():
1100 for i in OPTIONS.tempfiles:
1101 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001102 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001103 else:
1104 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001105 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001106
1107
1108class PasswordManager(object):
1109 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001110 self.editor = os.getenv("EDITOR")
1111 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001112
1113 def GetPasswords(self, items):
1114 """Get passwords corresponding to each string in 'items',
1115 returning a dict. (The dict may have keys in addition to the
1116 values in 'items'.)
1117
1118 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1119 user edit that file to add more needed passwords. If no editor is
1120 available, or $ANDROID_PW_FILE isn't define, prompts the user
1121 interactively in the ordinary way.
1122 """
1123
1124 current = self.ReadFile()
1125
1126 first = True
1127 while True:
1128 missing = []
1129 for i in items:
1130 if i not in current or not current[i]:
1131 missing.append(i)
1132 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001133 if not missing:
1134 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001135
1136 for i in missing:
1137 current[i] = ""
1138
1139 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001140 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001141 answer = raw_input("try to edit again? [y]> ").strip()
1142 if answer and answer[0] not in 'yY':
1143 raise RuntimeError("key passwords unavailable")
1144 first = False
1145
1146 current = self.UpdateAndReadFile(current)
1147
Dan Albert8b72aef2015-03-23 19:13:21 -07001148 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001149 """Prompt the user to enter a value (password) for each key in
1150 'current' whose value is fales. Returns a new dict with all the
1151 values.
1152 """
1153 result = {}
1154 for k, v in sorted(current.iteritems()):
1155 if v:
1156 result[k] = v
1157 else:
1158 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001159 result[k] = getpass.getpass(
1160 "Enter password for %s key> " % k).strip()
1161 if result[k]:
1162 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001163 return result
1164
1165 def UpdateAndReadFile(self, current):
1166 if not self.editor or not self.pwfile:
1167 return self.PromptResult(current)
1168
1169 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001170 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001171 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1172 f.write("# (Additional spaces are harmless.)\n\n")
1173
1174 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001175 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1176 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001177 f.write("[[[ %s ]]] %s\n" % (v, k))
1178 if not v and first_line is None:
1179 # position cursor on first line with no password.
1180 first_line = i + 4
1181 f.close()
1182
1183 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1184 _, _ = p.communicate()
1185
1186 return self.ReadFile()
1187
1188 def ReadFile(self):
1189 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001190 if self.pwfile is None:
1191 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001192 try:
1193 f = open(self.pwfile, "r")
1194 for line in f:
1195 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001196 if not line or line[0] == '#':
1197 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001198 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1199 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001200 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001201 else:
1202 result[m.group(2)] = m.group(1)
1203 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001204 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001205 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001206 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001207 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001208
1209
Dan Albert8e0178d2015-01-27 15:53:15 -08001210def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1211 compress_type=None):
1212 import datetime
1213
1214 # http://b/18015246
1215 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1216 # for files larger than 2GiB. We can work around this by adjusting their
1217 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1218 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1219 # it isn't clear to me exactly what circumstances cause this).
1220 # `zipfile.write()` must be used directly to work around this.
1221 #
1222 # This mess can be avoided if we port to python3.
1223 saved_zip64_limit = zipfile.ZIP64_LIMIT
1224 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1225
1226 if compress_type is None:
1227 compress_type = zip_file.compression
1228 if arcname is None:
1229 arcname = filename
1230
1231 saved_stat = os.stat(filename)
1232
1233 try:
1234 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1235 # file to be zipped and reset it when we're done.
1236 os.chmod(filename, perms)
1237
1238 # Use a fixed timestamp so the output is repeatable.
1239 epoch = datetime.datetime.fromtimestamp(0)
1240 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1241 os.utime(filename, (timestamp, timestamp))
1242
1243 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1244 finally:
1245 os.chmod(filename, saved_stat.st_mode)
1246 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1247 zipfile.ZIP64_LIMIT = saved_zip64_limit
1248
1249
Tao Bao58c1b962015-05-20 09:32:18 -07001250def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001251 compress_type=None):
1252 """Wrap zipfile.writestr() function to work around the zip64 limit.
1253
1254 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1255 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1256 when calling crc32(bytes).
1257
1258 But it still works fine to write a shorter string into a large zip file.
1259 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1260 when we know the string won't be too long.
1261 """
1262
1263 saved_zip64_limit = zipfile.ZIP64_LIMIT
1264 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1265
1266 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1267 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001268 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001269 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001270 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001271 else:
Tao Baof3282b42015-04-01 11:21:55 -07001272 zinfo = zinfo_or_arcname
1273
1274 # If compress_type is given, it overrides the value in zinfo.
1275 if compress_type is not None:
1276 zinfo.compress_type = compress_type
1277
Tao Bao58c1b962015-05-20 09:32:18 -07001278 # If perms is given, it has a priority.
1279 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001280 # If perms doesn't set the file type, mark it as a regular file.
1281 if perms & 0o770000 == 0:
1282 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001283 zinfo.external_attr = perms << 16
1284
Tao Baof3282b42015-04-01 11:21:55 -07001285 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001286 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1287
Dan Albert8b72aef2015-03-23 19:13:21 -07001288 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001289 zipfile.ZIP64_LIMIT = saved_zip64_limit
1290
1291
Tao Bao89d7ab22017-12-14 17:05:33 -08001292def ZipDelete(zip_filename, entries):
1293 """Deletes entries from a ZIP file.
1294
1295 Since deleting entries from a ZIP file is not supported, it shells out to
1296 'zip -d'.
1297
1298 Args:
1299 zip_filename: The name of the ZIP file.
1300 entries: The name of the entry, or the list of names to be deleted.
1301
1302 Raises:
1303 AssertionError: In case of non-zero return from 'zip'.
1304 """
1305 if isinstance(entries, basestring):
1306 entries = [entries]
1307 cmd = ["zip", "-d", zip_filename] + entries
1308 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1309 stdoutdata, _ = proc.communicate()
1310 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1311 stdoutdata)
1312
1313
Tao Baof3282b42015-04-01 11:21:55 -07001314def ZipClose(zip_file):
1315 # http://b/18015246
1316 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1317 # central directory.
1318 saved_zip64_limit = zipfile.ZIP64_LIMIT
1319 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1320
1321 zip_file.close()
1322
1323 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001324
1325
1326class DeviceSpecificParams(object):
1327 module = None
1328 def __init__(self, **kwargs):
1329 """Keyword arguments to the constructor become attributes of this
1330 object, which is passed to all functions in the device-specific
1331 module."""
1332 for k, v in kwargs.iteritems():
1333 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001334 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001335
1336 if self.module is None:
1337 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001338 if not path:
1339 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001340 try:
1341 if os.path.isdir(path):
1342 info = imp.find_module("releasetools", [path])
1343 else:
1344 d, f = os.path.split(path)
1345 b, x = os.path.splitext(f)
1346 if x == ".py":
1347 f = b
1348 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001349 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001350 self.module = imp.load_module("device_specific", *info)
1351 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001352 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001353
1354 def _DoCall(self, function_name, *args, **kwargs):
1355 """Call the named function in the device-specific module, passing
1356 the given args and kwargs. The first argument to the call will be
1357 the DeviceSpecific object itself. If there is no module, or the
1358 module does not define the function, return the value of the
1359 'default' kwarg (which itself defaults to None)."""
1360 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001361 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001362 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1363
1364 def FullOTA_Assertions(self):
1365 """Called after emitting the block of assertions at the top of a
1366 full OTA package. Implementations can add whatever additional
1367 assertions they like."""
1368 return self._DoCall("FullOTA_Assertions")
1369
Doug Zongkere5ff5902012-01-17 10:55:37 -08001370 def FullOTA_InstallBegin(self):
1371 """Called at the start of full OTA installation."""
1372 return self._DoCall("FullOTA_InstallBegin")
1373
Doug Zongker05d3dea2009-06-22 11:32:31 -07001374 def FullOTA_InstallEnd(self):
1375 """Called at the end of full OTA installation; typically this is
1376 used to install the image for the device's baseband processor."""
1377 return self._DoCall("FullOTA_InstallEnd")
1378
1379 def IncrementalOTA_Assertions(self):
1380 """Called after emitting the block of assertions at the top of an
1381 incremental OTA package. Implementations can add whatever
1382 additional assertions they like."""
1383 return self._DoCall("IncrementalOTA_Assertions")
1384
Doug Zongkere5ff5902012-01-17 10:55:37 -08001385 def IncrementalOTA_VerifyBegin(self):
1386 """Called at the start of the verification phase of incremental
1387 OTA installation; additional checks can be placed here to abort
1388 the script before any changes are made."""
1389 return self._DoCall("IncrementalOTA_VerifyBegin")
1390
Doug Zongker05d3dea2009-06-22 11:32:31 -07001391 def IncrementalOTA_VerifyEnd(self):
1392 """Called at the end of the verification phase of incremental OTA
1393 installation; additional checks can be placed here to abort the
1394 script before any changes are made."""
1395 return self._DoCall("IncrementalOTA_VerifyEnd")
1396
Doug Zongkere5ff5902012-01-17 10:55:37 -08001397 def IncrementalOTA_InstallBegin(self):
1398 """Called at the start of incremental OTA installation (after
1399 verification is complete)."""
1400 return self._DoCall("IncrementalOTA_InstallBegin")
1401
Doug Zongker05d3dea2009-06-22 11:32:31 -07001402 def IncrementalOTA_InstallEnd(self):
1403 """Called at the end of incremental OTA installation; typically
1404 this is used to install the image for the device's baseband
1405 processor."""
1406 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001407
Tao Bao9bc6bb22015-11-09 16:58:28 -08001408 def VerifyOTA_Assertions(self):
1409 return self._DoCall("VerifyOTA_Assertions")
1410
Tao Bao76def242017-11-21 09:25:31 -08001411
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001412class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001413 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001414 self.name = name
1415 self.data = data
1416 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001417 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001418 self.sha1 = sha1(data).hexdigest()
1419
1420 @classmethod
1421 def FromLocalFile(cls, name, diskname):
1422 f = open(diskname, "rb")
1423 data = f.read()
1424 f.close()
1425 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001426
1427 def WriteToTemp(self):
1428 t = tempfile.NamedTemporaryFile()
1429 t.write(self.data)
1430 t.flush()
1431 return t
1432
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001433 def WriteToDir(self, d):
1434 with open(os.path.join(d, self.name), "wb") as fp:
1435 fp.write(self.data)
1436
Geremy Condra36bd3652014-02-06 19:45:10 -08001437 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001438 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001439
Tao Bao76def242017-11-21 09:25:31 -08001440
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001441DIFF_PROGRAM_BY_EXT = {
1442 ".gz" : "imgdiff",
1443 ".zip" : ["imgdiff", "-z"],
1444 ".jar" : ["imgdiff", "-z"],
1445 ".apk" : ["imgdiff", "-z"],
1446 ".img" : "imgdiff",
1447 }
1448
Tao Bao76def242017-11-21 09:25:31 -08001449
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001450class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001451 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001452 self.tf = tf
1453 self.sf = sf
1454 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001455 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001456
1457 def ComputePatch(self):
1458 """Compute the patch (as a string of data) needed to turn sf into
1459 tf. Returns the same tuple as GetPatch()."""
1460
1461 tf = self.tf
1462 sf = self.sf
1463
Doug Zongker24cd2802012-08-14 16:36:15 -07001464 if self.diff_program:
1465 diff_program = self.diff_program
1466 else:
1467 ext = os.path.splitext(tf.name)[1]
1468 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001469
1470 ttemp = tf.WriteToTemp()
1471 stemp = sf.WriteToTemp()
1472
1473 ext = os.path.splitext(tf.name)[1]
1474
1475 try:
1476 ptemp = tempfile.NamedTemporaryFile()
1477 if isinstance(diff_program, list):
1478 cmd = copy.copy(diff_program)
1479 else:
1480 cmd = [diff_program]
1481 cmd.append(stemp.name)
1482 cmd.append(ttemp.name)
1483 cmd.append(ptemp.name)
1484 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001485 err = []
1486 def run():
1487 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001488 if e:
1489 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001490 th = threading.Thread(target=run)
1491 th.start()
1492 th.join(timeout=300) # 5 mins
1493 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001494 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001495 p.terminate()
1496 th.join(5)
1497 if th.is_alive():
1498 p.kill()
1499 th.join()
1500
Tianjie Xua2a9f992018-01-05 15:15:54 -08001501 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001502 print("WARNING: failure running %s:\n%s\n" % (
1503 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001504 self.patch = None
1505 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001506 diff = ptemp.read()
1507 finally:
1508 ptemp.close()
1509 stemp.close()
1510 ttemp.close()
1511
1512 self.patch = diff
1513 return self.tf, self.sf, self.patch
1514
1515
1516 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001517 """Returns a tuple of (target_file, source_file, patch_data).
1518
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001519 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001520 computing the patch failed.
1521 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001522 return self.tf, self.sf, self.patch
1523
1524
1525def ComputeDifferences(diffs):
1526 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001527 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001528
1529 # Do the largest files first, to try and reduce the long-pole effect.
1530 by_size = [(i.tf.size, i) for i in diffs]
1531 by_size.sort(reverse=True)
1532 by_size = [i[1] for i in by_size]
1533
1534 lock = threading.Lock()
1535 diff_iter = iter(by_size) # accessed under lock
1536
1537 def worker():
1538 try:
1539 lock.acquire()
1540 for d in diff_iter:
1541 lock.release()
1542 start = time.time()
1543 d.ComputePatch()
1544 dur = time.time() - start
1545 lock.acquire()
1546
1547 tf, sf, patch = d.GetPatch()
1548 if sf.name == tf.name:
1549 name = tf.name
1550 else:
1551 name = "%s (%s)" % (tf.name, sf.name)
1552 if patch is None:
Tao Bao76def242017-11-21 09:25:31 -08001553 print(
1554 "patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001555 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001556 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1557 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001558 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001559 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001560 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001561 raise
1562
1563 # start worker threads; wait for them all to finish.
1564 threads = [threading.Thread(target=worker)
1565 for i in range(OPTIONS.worker_threads)]
1566 for th in threads:
1567 th.start()
1568 while threads:
1569 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001570
1571
Dan Albert8b72aef2015-03-23 19:13:21 -07001572class BlockDifference(object):
1573 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001574 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001575 self.tgt = tgt
1576 self.src = src
1577 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001578 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001579 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001580
Tao Baodd2a5892015-03-12 12:32:37 -07001581 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001582 version = max(
1583 int(i) for i in
1584 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001585 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001586 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001587
1588 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001589 version=self.version,
1590 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001591 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001592 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001593 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001594 self.touched_src_ranges = b.touched_src_ranges
1595 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001596
Tao Baoaac4ad52015-10-16 15:26:34 -07001597 if src is None:
1598 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1599 else:
1600 _, self.device = GetTypeAndDevice("/" + partition,
1601 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001602
Tao Baod8d14be2016-02-04 14:26:02 -08001603 @property
1604 def required_cache(self):
1605 return self._required_cache
1606
Tao Bao76def242017-11-21 09:25:31 -08001607 def WriteScript(self, script, output_zip, progress=None,
1608 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001609 if not self.src:
1610 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001611 script.Print("Patching %s image unconditionally..." % (self.partition,))
1612 else:
1613 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001614
Dan Albert8b72aef2015-03-23 19:13:21 -07001615 if progress:
1616 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001617 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001618
1619 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001620 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001621
Tao Bao9bc6bb22015-11-09 16:58:28 -08001622 def WriteStrictVerifyScript(self, script):
1623 """Verify all the blocks in the care_map, including clobbered blocks.
1624
1625 This differs from the WriteVerifyScript() function: a) it prints different
1626 error messages; b) it doesn't allow half-way updated images to pass the
1627 verification."""
1628
1629 partition = self.partition
1630 script.Print("Verifying %s..." % (partition,))
1631 ranges = self.tgt.care_map
1632 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001633 script.AppendExtra(
1634 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1635 'ui_print("\\"%s\\" has unexpected contents.");' % (
1636 self.device, ranges_str,
1637 self.tgt.TotalSha1(include_clobbered_blocks=True),
1638 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001639 script.AppendExtra("")
1640
Tao Baod522bdc2016-04-12 15:53:16 -07001641 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001642 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001643
1644 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001645 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001646 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001647
1648 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001649 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001650 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001651 ranges = self.touched_src_ranges
1652 expected_sha1 = self.touched_src_sha1
1653 else:
1654 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1655 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001656
1657 # No blocks to be checked, skipping.
1658 if not ranges:
1659 return
1660
Tao Bao5ece99d2015-05-12 11:42:31 -07001661 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001662 script.AppendExtra(
1663 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1664 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1665 '"%s.patch.dat")) then' % (
1666 self.device, ranges_str, expected_sha1,
1667 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001668 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001669 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001670
Tianjie Xufc3422a2015-12-15 11:53:59 -08001671 if self.version >= 4:
1672
1673 # Bug: 21124327
1674 # When generating incrementals for the system and vendor partitions in
1675 # version 4 or newer, explicitly check the first block (which contains
1676 # the superblock) of the partition to see if it's what we expect. If
1677 # this check fails, give an explicit log message about the partition
1678 # having been remounted R/W (the most likely explanation).
1679 if self.check_first_block:
1680 script.AppendExtra('check_first_block("%s");' % (self.device,))
1681
1682 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001683 if partition == "system":
1684 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1685 else:
1686 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001687 script.AppendExtra((
1688 'ifelse (block_image_recover("{device}", "{ranges}") && '
1689 'block_image_verify("{device}", '
1690 'package_extract_file("{partition}.transfer.list"), '
1691 '"{partition}.new.dat", "{partition}.patch.dat"), '
1692 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001693 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001694 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001695 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001696
Tao Baodd2a5892015-03-12 12:32:37 -07001697 # Abort the OTA update. Note that the incremental OTA cannot be applied
1698 # even if it may match the checksum of the target partition.
1699 # a) If version < 3, operations like move and erase will make changes
1700 # unconditionally and damage the partition.
1701 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001702 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001703 if partition == "system":
1704 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1705 else:
1706 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1707 script.AppendExtra((
1708 'abort("E%d: %s partition has unexpected contents");\n'
1709 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001710
Tao Bao5fcaaef2015-06-01 13:40:49 -07001711 def _WritePostInstallVerifyScript(self, script):
1712 partition = self.partition
1713 script.Print('Verifying the updated %s image...' % (partition,))
1714 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1715 ranges = self.tgt.care_map
1716 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001717 script.AppendExtra(
1718 'if range_sha1("%s", "%s") == "%s" then' % (
1719 self.device, ranges_str,
1720 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001721
1722 # Bug: 20881595
1723 # Verify that extended blocks are really zeroed out.
1724 if self.tgt.extended:
1725 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001726 script.AppendExtra(
1727 'if range_sha1("%s", "%s") == "%s" then' % (
1728 self.device, ranges_str,
1729 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001730 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001731 if partition == "system":
1732 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1733 else:
1734 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001735 script.AppendExtra(
1736 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001737 ' abort("E%d: %s partition has unexpected non-zero contents after '
1738 'OTA update");\n'
1739 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001740 else:
1741 script.Print('Verified the updated %s image.' % (partition,))
1742
Tianjie Xu209db462016-05-24 17:34:52 -07001743 if partition == "system":
1744 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1745 else:
1746 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1747
Tao Bao5fcaaef2015-06-01 13:40:49 -07001748 script.AppendExtra(
1749 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001750 ' abort("E%d: %s partition has unexpected contents after OTA '
1751 'update");\n'
1752 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001753
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001754 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001755 ZipWrite(output_zip,
1756 '{}.transfer.list'.format(self.path),
1757 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001758
Tao Bao76def242017-11-21 09:25:31 -08001759 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1760 # its size. Quailty 9 almost triples the compression time but doesn't
1761 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001762 # zip | brotli(quality 6) | brotli(quality 9)
1763 # compressed_size: 942M | 869M (~8% reduced) | 854M
1764 # compression_time: 75s | 265s | 719s
1765 # decompression_time: 15s | 25s | 25s
1766
1767 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001768 brotli_cmd = ['brotli', '--quality=6',
1769 '--output={}.new.dat.br'.format(self.path),
1770 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001771 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao80921982018-03-21 21:02:19 -07001772 p = Run(brotli_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1773 stdoutdata, _ = p.communicate()
1774 assert p.returncode == 0, \
1775 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1776 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001777
1778 new_data_name = '{}.new.dat.br'.format(self.partition)
1779 ZipWrite(output_zip,
1780 '{}.new.dat.br'.format(self.path),
1781 new_data_name,
1782 compress_type=zipfile.ZIP_STORED)
1783 else:
1784 new_data_name = '{}.new.dat'.format(self.partition)
1785 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1786
Dan Albert8e0178d2015-01-27 15:53:15 -08001787 ZipWrite(output_zip,
1788 '{}.patch.dat'.format(self.path),
1789 '{}.patch.dat'.format(self.partition),
1790 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001791
Tianjie Xu209db462016-05-24 17:34:52 -07001792 if self.partition == "system":
1793 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1794 else:
1795 code = ErrorCode.VENDOR_UPDATE_FAILURE
1796
Dan Albert8e0178d2015-01-27 15:53:15 -08001797 call = ('block_image_update("{device}", '
1798 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001799 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001800 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001801 device=self.device, partition=self.partition,
1802 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001803 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001804
Dan Albert8b72aef2015-03-23 19:13:21 -07001805 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001806 data = source.ReadRangeSet(ranges)
1807 ctx = sha1()
1808
1809 for p in data:
1810 ctx.update(p)
1811
1812 return ctx.hexdigest()
1813
Tao Baoe9b61912015-07-09 17:37:49 -07001814 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1815 """Return the hash value for all zero blocks."""
1816 zero_block = '\x00' * 4096
1817 ctx = sha1()
1818 for _ in range(num_blocks):
1819 ctx.update(zero_block)
1820
1821 return ctx.hexdigest()
1822
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001823
1824DataImage = blockimgdiff.DataImage
1825
Tao Bao76def242017-11-21 09:25:31 -08001826
Doug Zongker96a57e72010-09-26 14:57:41 -07001827# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001828PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001829 "ext4": "EMMC",
1830 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001831 "f2fs": "EMMC",
1832 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001833}
Doug Zongker96a57e72010-09-26 14:57:41 -07001834
Tao Bao76def242017-11-21 09:25:31 -08001835
Doug Zongker96a57e72010-09-26 14:57:41 -07001836def GetTypeAndDevice(mount_point, info):
1837 fstab = info["fstab"]
1838 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001839 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1840 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001841 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001842 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001843
1844
1845def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001846 """Parses and converts a PEM-encoded certificate into DER-encoded.
1847
1848 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1849
1850 Returns:
1851 The decoded certificate string.
1852 """
1853 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001854 save = False
1855 for line in data.split("\n"):
1856 if "--END CERTIFICATE--" in line:
1857 break
1858 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001859 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001860 if "--BEGIN CERTIFICATE--" in line:
1861 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001862 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001863 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001864
Tao Bao04e1f012018-02-04 12:13:35 -08001865
1866def ExtractPublicKey(cert):
1867 """Extracts the public key (PEM-encoded) from the given certificate file.
1868
1869 Args:
1870 cert: The certificate filename.
1871
1872 Returns:
1873 The public key string.
1874
1875 Raises:
1876 AssertionError: On non-zero return from 'openssl'.
1877 """
1878 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1879 # While openssl 1.1 writes the key into the given filename followed by '-out',
1880 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1881 # stdout instead.
1882 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1883 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1884 pubkey, stderrdata = proc.communicate()
1885 assert proc.returncode == 0, \
1886 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1887 return pubkey
1888
1889
Doug Zongker412c02f2014-02-13 10:58:24 -08001890def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1891 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001892 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001893
Tao Bao6d5d6232018-03-09 17:04:42 -08001894 Most of the space in the boot and recovery images is just the kernel, which is
1895 identical for the two, so the resulting patch should be efficient. Add it to
1896 the output zip, along with a shell script that is run from init.rc on first
1897 boot to actually do the patching and install the new recovery image.
1898
1899 Args:
1900 input_dir: The top-level input directory of the target-files.zip.
1901 output_sink: The callback function that writes the result.
1902 recovery_img: File object for the recovery image.
1903 boot_img: File objects for the boot image.
1904 info_dict: A dict returned by common.LoadInfoDict() on the input
1905 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001906 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001907 if info_dict is None:
1908 info_dict = OPTIONS.info_dict
1909
Tao Bao6d5d6232018-03-09 17:04:42 -08001910 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001911
Tao Baof2cffbd2015-07-22 12:33:18 -07001912 if full_recovery_image:
1913 output_sink("etc/recovery.img", recovery_img.data)
1914
1915 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08001916 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07001917 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08001918 # With system-root-image, boot and recovery images will have mismatching
1919 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
1920 # to handle such a case.
1921 if system_root_image:
1922 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07001923 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08001924 assert not os.path.exists(path)
1925 else:
1926 diff_program = ["imgdiff"]
1927 if os.path.exists(path):
1928 diff_program.append("-b")
1929 diff_program.append(path)
1930 bonus_args = "-b /system/etc/recovery-resource.dat"
1931 else:
1932 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07001933
1934 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1935 _, _, patch = d.ComputePatch()
1936 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001937
Dan Albertebb19aa2015-03-27 19:11:53 -07001938 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001939 # The following GetTypeAndDevice()s need to use the path in the target
1940 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001941 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1942 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1943 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001944 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001945
Tao Baof2cffbd2015-07-22 12:33:18 -07001946 if full_recovery_image:
1947 sh = """#!/system/bin/sh
1948if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1949 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"
1950else
1951 log -t recovery "Recovery image already installed"
1952fi
1953""" % {'type': recovery_type,
1954 'device': recovery_device,
1955 'sha1': recovery_img.sha1,
1956 'size': recovery_img.size}
1957 else:
1958 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001959if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1960 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"
1961else
1962 log -t recovery "Recovery image already installed"
1963fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001964""" % {'boot_size': boot_img.size,
1965 'boot_sha1': boot_img.sha1,
1966 'recovery_size': recovery_img.size,
1967 'recovery_sha1': recovery_img.sha1,
1968 'boot_type': boot_type,
1969 'boot_device': boot_device,
1970 'recovery_type': recovery_type,
1971 'recovery_device': recovery_device,
1972 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001973
1974 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001975 # in the L release.
1976 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001977
Tao Bao89fbb0f2017-01-10 10:47:58 -08001978 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001979
1980 output_sink(sh_location, sh)