blob: 5b5e6edfb9642382a2ecce1d190d47ad59a81208 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Doug Zongkerea5d7a92010-09-12 15:26:16 -070017import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070018import errno
Doug Zongkereef39442009-04-02 12:14:19 -070019import getopt
20import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010021import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070022import imp
Doug Zongkereef39442009-04-02 12:14:19 -070023import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080024import platform
Doug Zongkereef39442009-04-02 12:14:19 -070025import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070026import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070027import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080028import string
Doug Zongkereef39442009-04-02 12:14:19 -070029import subprocess
30import sys
31import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070032import threading
33import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070034import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080035from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070036
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070037import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080038import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070039
Dan Albert8b72aef2015-03-23 19:13:21 -070040class Options(object):
41 def __init__(self):
42 platform_search_path = {
43 "linux2": "out/host/linux-x86",
44 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070045 }
Doug Zongker85448772014-09-09 14:59:20 -070046
Tao Bao76def242017-11-21 09:25:31 -080047 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080049 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070050 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080052 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070053 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070055 # use otatools built boot_signer by default
56 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070057 self.boot_signer_args = []
58 self.verity_signer_path = None
59 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.verbose = False
61 self.tempfiles = []
62 self.device_specific = None
63 self.extras = {}
64 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070065 self.source_info_dict = None
66 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070068 # Stash size cannot exceed cache_size * threshold.
69 self.cache_size = None
70 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070071
72
73OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070074
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080075
76# Values for "certificate" in apkcerts that mean special things.
77SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
78
Tao Bao9dd909e2017-11-14 11:27:32 -080079
80# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010081AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
82 'product-services', 'dtbo')
Tao Bao9dd909e2017-11-14 11:27:32 -080083
84
Tianjie Xu209db462016-05-24 17:34:52 -070085class ErrorCode(object):
86 """Define error_codes for failures that happen during the actual
87 update package installation.
88
89 Error codes 0-999 are reserved for failures before the package
90 installation (i.e. low battery, package verification failure).
91 Detailed code in 'bootable/recovery/error_code.h' """
92
93 SYSTEM_VERIFICATION_FAILURE = 1000
94 SYSTEM_UPDATE_FAILURE = 1001
95 SYSTEM_UNEXPECTED_CONTENTS = 1002
96 SYSTEM_NONZERO_CONTENTS = 1003
97 SYSTEM_RECOVER_FAILURE = 1004
98 VENDOR_VERIFICATION_FAILURE = 2000
99 VENDOR_UPDATE_FAILURE = 2001
100 VENDOR_UNEXPECTED_CONTENTS = 2002
101 VENDOR_NONZERO_CONTENTS = 2003
102 VENDOR_RECOVER_FAILURE = 2004
103 OEM_PROP_MISMATCH = 3000
104 FINGERPRINT_MISMATCH = 3001
105 THUMBPRINT_MISMATCH = 3002
106 OLDER_BUILD = 3003
107 DEVICE_MISMATCH = 3004
108 BAD_PATCH_FILE = 3005
109 INSUFFICIENT_CACHE_SPACE = 3006
110 TUNE_PARTITION_FAILURE = 3007
111 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800112
Tao Bao80921982018-03-21 21:02:19 -0700113
Dan Albert8b72aef2015-03-23 19:13:21 -0700114class ExternalError(RuntimeError):
115 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700116
117
Tao Bao39451582017-05-04 11:10:47 -0700118def Run(args, verbose=None, **kwargs):
119 """Create and return a subprocess.Popen object.
120
121 Caller can specify if the command line should be printed. The global
122 OPTIONS.verbose will be used if not specified.
123 """
124 if verbose is None:
125 verbose = OPTIONS.verbose
126 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800127 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700128 return subprocess.Popen(args, **kwargs)
129
130
Tao Baoc765cca2018-01-31 17:32:40 -0800131def RoundUpTo4K(value):
132 rounded_up = value + 4095
133 return rounded_up - (rounded_up % 4096)
134
135
Ying Wang7e6d4e42010-12-13 16:25:36 -0800136def CloseInheritedPipes():
137 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
138 before doing other work."""
139 if platform.system() != "Darwin":
140 return
141 for d in range(3, 1025):
142 try:
143 stat = os.fstat(d)
144 if stat is not None:
145 pipebit = stat[0] & 0x1000
146 if pipebit != 0:
147 os.close(d)
148 except OSError:
149 pass
150
151
Tao Bao2c15d9e2015-07-09 11:51:16 -0700152def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700153 """Read and parse the META/misc_info.txt key/value pairs from the
154 input target files and return a dict."""
155
Doug Zongkerc9253822014-02-04 12:17:58 -0800156 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700157 if isinstance(input_file, zipfile.ZipFile):
158 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800159 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700160 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800161 try:
162 with open(path) as f:
163 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700164 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800165 if e.errno == errno.ENOENT:
166 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800167
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700168 try:
Michael Runge6e836112014-04-15 17:40:21 -0700169 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700170 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800171 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700172
Tao Bao6cd54732017-02-27 15:12:05 -0800173 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800174 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800175
Tao Bao84e75682015-07-19 02:38:53 -0700176 # A few properties are stored as links to the files in the out/ directory.
177 # It works fine with the build system. However, they are no longer available
178 # when (re)generating from target_files zip. If input_dir is not None, we
179 # are doing repacking. Redirect those properties to the actual files in the
180 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700181 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400182 # We carry a copy of file_contexts.bin under META/. If not available,
183 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700184 # to build images than the one running on device, such as when enabling
185 # system_root_image. In that case, we must have the one for image
186 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700187 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
188 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700189 if d.get("system_root_image") == "true":
190 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700191 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700192 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700193 if not os.path.exists(fc_config):
194 fc_config = None
195
196 if fc_config:
197 d["selinux_fc"] = fc_config
198
Tao Bao8bfd3c72018-07-20 15:20:28 -0700199 # Similarly we need to redirect "root_dir" and "root_fs_config".
Tao Bao84e75682015-07-19 02:38:53 -0700200 if d.get("system_root_image") == "true":
Tao Bao8bfd3c72018-07-20 15:20:28 -0700201 d["root_dir"] = os.path.join(input_dir, "ROOT")
202 d["root_fs_config"] = os.path.join(
Tao Bao84e75682015-07-19 02:38:53 -0700203 input_dir, "META", "root_filesystem_config.txt")
204
Tao Baof54216f2016-03-29 15:12:37 -0700205 # Redirect {system,vendor}_base_fs_file.
206 if "system_base_fs_file" in d:
207 basename = os.path.basename(d["system_base_fs_file"])
208 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700209 if os.path.exists(system_base_fs_file):
210 d["system_base_fs_file"] = system_base_fs_file
211 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800212 print("Warning: failed to find system base fs file: %s" % (
213 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700214 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700215
216 if "vendor_base_fs_file" in d:
217 basename = os.path.basename(d["vendor_base_fs_file"])
218 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700219 if os.path.exists(vendor_base_fs_file):
220 d["vendor_base_fs_file"] = vendor_base_fs_file
221 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800222 print("Warning: failed to find vendor base fs file: %s" % (
223 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700224 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700225
Doug Zongker37974732010-09-16 17:44:38 -0700226 def makeint(key):
227 if key in d:
228 d[key] = int(d[key], 0)
229
230 makeint("recovery_api_version")
231 makeint("blocksize")
232 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700233 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700234 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700235 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700236 makeint("recovery_size")
237 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800238 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700239
Tao Bao76def242017-11-21 09:25:31 -0800240 system_root_image = d.get("system_root_image") == "true"
241 if d.get("no_recovery") != "true":
Tianjie Xucfa86222016-03-07 16:31:19 -0800242 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800243 d["fstab"] = LoadRecoveryFSTab(
244 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
245 elif d.get("recovery_as_boot") == "true":
Tianjie Xucfa86222016-03-07 16:31:19 -0800246 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800247 d["fstab"] = LoadRecoveryFSTab(
248 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tianjie Xucfa86222016-03-07 16:31:19 -0800249 else:
250 d["fstab"] = None
251
Tao Baobcd1d162017-08-26 13:10:26 -0700252 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
253 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Tao Bao12d87fc2018-01-31 12:18:52 -0800254
255 # Set up the salt (based on fingerprint or thumbprint) that will be used when
256 # adding AVB footer.
257 if d.get("avb_enable") == "true":
258 fp = None
259 if "build.prop" in d:
260 build_prop = d["build.prop"]
261 if "ro.build.fingerprint" in build_prop:
262 fp = build_prop["ro.build.fingerprint"]
263 elif "ro.build.thumbprint" in build_prop:
264 fp = build_prop["ro.build.thumbprint"]
265 if fp:
266 d["avb_salt"] = sha256(fp).hexdigest()
267
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700268 return d
269
Tao Baod1de6f32017-03-01 16:38:48 -0800270
Tao Baobcd1d162017-08-26 13:10:26 -0700271def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700272 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700273 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700274 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700275 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700276 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700277 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700278
Tao Baod1de6f32017-03-01 16:38:48 -0800279
Michael Runge6e836112014-04-15 17:40:21 -0700280def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700281 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700282 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700283 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700284 if not line or line.startswith("#"):
285 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700286 if "=" in line:
287 name, value = line.split("=", 1)
288 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700289 return d
290
Tao Baod1de6f32017-03-01 16:38:48 -0800291
Tianjie Xucfa86222016-03-07 16:31:19 -0800292def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
293 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700294 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800295 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700296 self.mount_point = mount_point
297 self.fs_type = fs_type
298 self.device = device
299 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700300 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700301
302 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800303 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700304 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800305 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700306 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700307
Tao Baod1de6f32017-03-01 16:38:48 -0800308 assert fstab_version == 2
309
310 d = {}
311 for line in data.split("\n"):
312 line = line.strip()
313 if not line or line.startswith("#"):
314 continue
315
316 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
317 pieces = line.split()
318 if len(pieces) != 5:
319 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
320
321 # Ignore entries that are managed by vold.
322 options = pieces[4]
323 if "voldmanaged=" in options:
324 continue
325
326 # It's a good line, parse it.
327 length = 0
328 options = options.split(",")
329 for i in options:
330 if i.startswith("length="):
331 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800332 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800333 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700334 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800335
Tao Baod1de6f32017-03-01 16:38:48 -0800336 mount_flags = pieces[3]
337 # Honor the SELinux context if present.
338 context = None
339 for i in mount_flags.split(","):
340 if i.startswith("context="):
341 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800342
Tao Baod1de6f32017-03-01 16:38:48 -0800343 mount_point = pieces[1]
344 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
345 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800346
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700347 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700348 # system. Other areas assume system is always at "/system" so point /system
349 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700350 if system_root_image:
351 assert not d.has_key("/system") and d.has_key("/")
352 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700353 return d
354
355
Doug Zongker37974732010-09-16 17:44:38 -0700356def DumpInfoDict(d):
357 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800358 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700359
Dan Albert8b72aef2015-03-23 19:13:21 -0700360
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800361def AppendAVBSigningArgs(cmd, partition):
362 """Append signing arguments for avbtool."""
363 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
364 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
365 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
366 if key_path and algorithm:
367 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700368 avb_salt = OPTIONS.info_dict.get("avb_salt")
369 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
370 if avb_salt and partition != "vbmeta":
371 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800372
373
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700374def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800375 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700376 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700377
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700378 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800379 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
380 we are building a two-step special image (i.e. building a recovery image to
381 be loaded into /boot in two-step OTAs).
382
383 Return the image data, or None if sourcedir does not appear to contains files
384 for building the requested image.
385 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700386
387 def make_ramdisk():
388 ramdisk_img = tempfile.NamedTemporaryFile()
389
390 if os.access(fs_config_file, os.F_OK):
391 cmd = ["mkbootfs", "-f", fs_config_file,
392 os.path.join(sourcedir, "RAMDISK")]
393 else:
394 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
395 p1 = Run(cmd, stdout=subprocess.PIPE)
396 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
397
398 p2.wait()
399 p1.wait()
400 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
401 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
402
403 return ramdisk_img
404
405 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
406 return None
407
408 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700409 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700410
Doug Zongkerd5131602012-08-02 14:46:42 -0700411 if info_dict is None:
412 info_dict = OPTIONS.info_dict
413
Doug Zongkereef39442009-04-02 12:14:19 -0700414 img = tempfile.NamedTemporaryFile()
415
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700416 if has_ramdisk:
417 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700418
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800419 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
420 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
421
422 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700423
Benoit Fradina45a8682014-07-14 21:00:43 +0200424 fn = os.path.join(sourcedir, "second")
425 if os.access(fn, os.F_OK):
426 cmd.append("--second")
427 cmd.append(fn)
428
Doug Zongker171f1cd2009-06-15 22:36:37 -0700429 fn = os.path.join(sourcedir, "cmdline")
430 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700431 cmd.append("--cmdline")
432 cmd.append(open(fn).read().rstrip("\n"))
433
434 fn = os.path.join(sourcedir, "base")
435 if os.access(fn, os.F_OK):
436 cmd.append("--base")
437 cmd.append(open(fn).read().rstrip("\n"))
438
Ying Wang4de6b5b2010-08-25 14:29:34 -0700439 fn = os.path.join(sourcedir, "pagesize")
440 if os.access(fn, os.F_OK):
441 cmd.append("--pagesize")
442 cmd.append(open(fn).read().rstrip("\n"))
443
Tao Bao76def242017-11-21 09:25:31 -0800444 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700445 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700446 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700447
Tao Bao76def242017-11-21 09:25:31 -0800448 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000449 if args and args.strip():
450 cmd.extend(shlex.split(args))
451
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700452 if has_ramdisk:
453 cmd.extend(["--ramdisk", ramdisk_img.name])
454
Tao Baod95e9fd2015-03-29 23:07:41 -0700455 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800456 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700457 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700458 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700459 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700460 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700461
Tao Baobf70c3182017-07-11 17:27:55 -0700462 # "boot" or "recovery", without extension.
463 partition_name = os.path.basename(sourcedir).lower()
464
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700465 if (partition_name == "recovery" and
466 info_dict.get("include_recovery_dtbo") == "true"):
467 fn = os.path.join(sourcedir, "recovery_dtbo")
468 cmd.extend(["--recovery_dtbo", fn])
469
Doug Zongker38a649f2009-06-17 09:07:09 -0700470 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700471 p.communicate()
Tao Baobf70c3182017-07-11 17:27:55 -0700472 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700473
Tao Bao76def242017-11-21 09:25:31 -0800474 if (info_dict.get("boot_signer") == "true" and
475 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800476 # Hard-code the path as "/boot" for two-step special recovery image (which
477 # will be loaded into /boot during the two-step OTA).
478 if two_step_image:
479 path = "/boot"
480 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700481 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700482 cmd = [OPTIONS.boot_signer_path]
483 cmd.extend(OPTIONS.boot_signer_args)
484 cmd.extend([path, img.name,
485 info_dict["verity_key"] + ".pk8",
486 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700487 p = Run(cmd, stdout=subprocess.PIPE)
488 p.communicate()
489 assert p.returncode == 0, "boot_signer of %s image failed" % path
490
Tao Baod95e9fd2015-03-29 23:07:41 -0700491 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800492 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700493 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700494 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800495 # We have switched from the prebuilt futility binary to using the tool
496 # (futility-host) built from the source. Override the setting in the old
497 # TF.zip.
498 futility = info_dict["futility"]
499 if futility.startswith("prebuilts/"):
500 futility = "futility-host"
501 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700502 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700503 info_dict["vboot_key"] + ".vbprivk",
504 info_dict["vboot_subkey"] + ".vbprivk",
505 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700506 img.name]
507 p = Run(cmd, stdout=subprocess.PIPE)
508 p.communicate()
509 assert p.returncode == 0, "vboot_signer of %s image failed" % path
510
Tao Baof3282b42015-04-01 11:21:55 -0700511 # Clean up the temp files.
512 img_unsigned.close()
513 img_keyblock.close()
514
David Zeuthen8fecb282017-12-01 16:24:01 -0500515 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800516 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700517 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500518 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400519 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700520 "--partition_size", str(part_size), "--partition_name",
521 partition_name]
522 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500523 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400524 if args and args.strip():
525 cmd.extend(shlex.split(args))
526 p = Run(cmd, stdout=subprocess.PIPE)
527 p.communicate()
528 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c3182017-07-11 17:27:55 -0700529 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500530
531 img.seek(os.SEEK_SET, 0)
532 data = img.read()
533
534 if has_ramdisk:
535 ramdisk_img.close()
536 img.close()
537
538 return data
539
540
Doug Zongkerd5131602012-08-02 14:46:42 -0700541def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800542 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700543 """Return a File object with the desired bootable image.
544
545 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
546 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
547 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700548
Doug Zongker55d93282011-01-25 17:03:34 -0800549 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
550 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800551 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800552 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700553
554 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
555 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800556 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700557 return File.FromLocalFile(name, prebuilt_path)
558
Tao Bao89fbb0f2017-01-10 10:47:58 -0800559 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700560
561 if info_dict is None:
562 info_dict = OPTIONS.info_dict
563
564 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800565 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
566 # for recovery.
567 has_ramdisk = (info_dict.get("system_root_image") != "true" or
568 prebuilt_name != "boot.img" or
569 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700570
Doug Zongker6f1d0312014-08-22 08:07:12 -0700571 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400572 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
573 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800574 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700575 if data:
576 return File(name, data)
577 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800578
Doug Zongkereef39442009-04-02 12:14:19 -0700579
Narayan Kamatha07bf042017-08-14 14:49:21 +0100580def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800581 """Gunzips the given gzip compressed file to a given output file."""
582 with gzip.open(in_filename, "rb") as in_file, \
583 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100584 shutil.copyfileobj(in_file, out_file)
585
586
Doug Zongker75f17362009-12-08 13:46:44 -0800587def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800588 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800589
Tao Bao1c830bf2017-12-25 10:43:47 -0800590 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
591 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800592
Tao Bao1c830bf2017-12-25 10:43:47 -0800593 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800594 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800595 """
Doug Zongkereef39442009-04-02 12:14:19 -0700596
Doug Zongker55d93282011-01-25 17:03:34 -0800597 def unzip_to_dir(filename, dirname):
598 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
599 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800600 cmd.extend(pattern)
Tao Bao80921982018-03-21 21:02:19 -0700601 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
602 stdoutdata, _ = p.communicate()
Doug Zongker55d93282011-01-25 17:03:34 -0800603 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700604 raise ExternalError(
605 "Failed to unzip input target-files \"{}\":\n{}".format(
606 filename, stdoutdata))
Doug Zongker55d93282011-01-25 17:03:34 -0800607
Tao Bao1c830bf2017-12-25 10:43:47 -0800608 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800609 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
610 if m:
611 unzip_to_dir(m.group(1), tmp)
612 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
613 filename = m.group(1)
614 else:
615 unzip_to_dir(filename, tmp)
616
Tao Baodba59ee2018-01-09 13:21:02 -0800617 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700618
619
Tao Baoe709b092018-02-07 12:40:00 -0800620def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -0800621 """Returns a SparseImage object suitable for passing to BlockImageDiff.
622
623 This function loads the specified sparse image from the given path, and
624 performs additional processing for OTA purpose. For example, it always adds
625 block 0 to clobbered blocks list. It also detects files that cannot be
626 reconstructed from the block list, for whom we should avoid applying imgdiff.
627
628 Args:
629 which: The partition name, which must be "system" or "vendor".
630 tmpdir: The directory that contains the prebuilt image and block map file.
631 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800632 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -0800633
634 Returns:
635 A SparseImage object, with file_map info loaded.
636 """
637 assert which in ("system", "vendor")
638
639 path = os.path.join(tmpdir, "IMAGES", which + ".img")
640 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
641
642 # The image and map files must have been created prior to calling
643 # ota_from_target_files.py (since LMP).
644 assert os.path.exists(path) and os.path.exists(mappath)
645
646 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
647 # it to clobbered_blocks so that it will be written to the target
648 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
649 clobbered_blocks = "0"
650
Tao Baoe709b092018-02-07 12:40:00 -0800651 image = sparse_img.SparseImage(path, mappath, clobbered_blocks,
652 allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -0800653
654 # block.map may contain less blocks, because mke2fs may skip allocating blocks
655 # if they contain all zeros. We can't reconstruct such a file from its block
656 # list. Tag such entries accordingly. (Bug: 65213616)
657 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800658 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700659 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800660 continue
661
Tao Baod3554e62018-07-10 15:31:22 -0700662 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that when
663 # using system_root_image, the filename listed in system.map may contain an
664 # additional leading slash (i.e. "//system/framework/am.jar"). Using lstrip
665 # to get consistent results.
666 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
667
668 # Special handling another case with system_root_image, where files not
669 # under /system (e.g. "/sbin/charger") are packed under ROOT/ in a
670 # target_files.zip.
671 if which == 'system' and not arcname.startswith('SYSTEM'):
672 arcname = 'ROOT/' + arcname
673
674 assert arcname in input_zip.namelist(), \
675 "Failed to find the ZIP entry for {}".format(entry)
676
Tao Baoc765cca2018-01-31 17:32:40 -0800677 info = input_zip.getinfo(arcname)
678 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800679
680 # If a RangeSet has been tagged as using shared blocks while loading the
681 # image, its block list must be already incomplete due to that reason. Don't
682 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
683 if ranges.extra.get('uses_shared_blocks'):
684 continue
685
Tao Baoc765cca2018-01-31 17:32:40 -0800686 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
687 ranges.extra['incomplete'] = True
688
689 return image
690
691
Doug Zongkereef39442009-04-02 12:14:19 -0700692def GetKeyPasswords(keylist):
693 """Given a list of keys, prompt the user to enter passwords for
694 those which require them. Return a {key: password} dict. password
695 will be None if the key has no password."""
696
Doug Zongker8ce7c252009-05-22 13:34:54 -0700697 no_passwords = []
698 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700699 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700700 devnull = open("/dev/null", "w+b")
701 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800702 # We don't need a password for things that aren't really keys.
703 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700704 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700705 continue
706
T.R. Fullhart37e10522013-03-18 10:31:26 -0700707 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700708 "-inform", "DER", "-nocrypt"],
709 stdin=devnull.fileno(),
710 stdout=devnull.fileno(),
711 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700712 p.communicate()
713 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700714 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700715 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700716 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700717 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
718 "-inform", "DER", "-passin", "pass:"],
719 stdin=devnull.fileno(),
720 stdout=devnull.fileno(),
721 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700722 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700723 if p.returncode == 0:
724 # Encrypted key with empty string as password.
725 key_passwords[k] = ''
726 elif stderr.startswith('Error decrypting key'):
727 # Definitely encrypted key.
728 # It would have said "Error reading key" if it didn't parse correctly.
729 need_passwords.append(k)
730 else:
731 # Potentially, a type of key that openssl doesn't understand.
732 # We'll let the routines in signapk.jar handle it.
733 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700734 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700735
T.R. Fullhart37e10522013-03-18 10:31:26 -0700736 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800737 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700738 return key_passwords
739
740
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800741def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700742 """Gets the minSdkVersion declared in the APK.
743
744 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
745 This can be both a decimal number (API Level) or a codename.
746
747 Args:
748 apk_name: The APK filename.
749
750 Returns:
751 The parsed SDK version string.
752
753 Raises:
754 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800755 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700756 proc = Run(
757 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
758 stderr=subprocess.PIPE)
759 stdoutdata, stderrdata = proc.communicate()
760 if proc.returncode != 0:
761 raise ExternalError(
762 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
763 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800764
Tao Baof47bf0f2018-03-21 23:28:51 -0700765 for line in stdoutdata.split("\n"):
766 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800767 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
768 if m:
769 return m.group(1)
770 raise ExternalError("No minSdkVersion returned by aapt")
771
772
773def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700774 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800775
Tao Baof47bf0f2018-03-21 23:28:51 -0700776 If minSdkVersion is set to a codename, it is translated to a number using the
777 provided map.
778
779 Args:
780 apk_name: The APK filename.
781
782 Returns:
783 The parsed SDK version number.
784
785 Raises:
786 ExternalError: On failing to get the min SDK version number.
787 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800788 version = GetMinSdkVersion(apk_name)
789 try:
790 return int(version)
791 except ValueError:
792 # Not a decimal number. Codename?
793 if version in codename_to_api_level_map:
794 return codename_to_api_level_map[version]
795 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700796 raise ExternalError(
797 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
798 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800799
800
801def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800802 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700803 """Sign the input_name zip/jar/apk, producing output_name. Use the
804 given key and password (the latter may be None if the key does not
805 have a password.
806
Doug Zongker951495f2009-08-14 12:44:19 -0700807 If whole_file is true, use the "-w" option to SignApk to embed a
808 signature that covers the whole file in the archive comment of the
809 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800810
811 min_api_level is the API Level (int) of the oldest platform this file may end
812 up on. If not specified for an APK, the API Level is obtained by interpreting
813 the minSdkVersion attribute of the APK's AndroidManifest.xml.
814
815 codename_to_api_level_map is needed to translate the codename which may be
816 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700817 """
Tao Bao76def242017-11-21 09:25:31 -0800818 if codename_to_api_level_map is None:
819 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700820
Alex Klyubin9667b182015-12-10 13:38:50 -0800821 java_library_path = os.path.join(
822 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
823
Tao Baoe95540e2016-11-08 12:08:53 -0800824 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
825 ["-Djava.library.path=" + java_library_path,
826 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
827 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700828 if whole_file:
829 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800830
831 min_sdk_version = min_api_level
832 if min_sdk_version is None:
833 if not whole_file:
834 min_sdk_version = GetMinSdkVersionInt(
835 input_name, codename_to_api_level_map)
836 if min_sdk_version is not None:
837 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
838
T.R. Fullhart37e10522013-03-18 10:31:26 -0700839 cmd.extend([key + OPTIONS.public_key_suffix,
840 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800841 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700842
Tao Bao80921982018-03-21 21:02:19 -0700843 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
844 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700845 if password is not None:
846 password += "\n"
Tao Bao80921982018-03-21 21:02:19 -0700847 stdoutdata, _ = p.communicate(password)
Doug Zongkereef39442009-04-02 12:14:19 -0700848 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700849 raise ExternalError(
850 "Failed to run signapk.jar: return code {}:\n{}".format(
851 p.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700852
Doug Zongkereef39442009-04-02 12:14:19 -0700853
Doug Zongker37974732010-09-16 17:44:38 -0700854def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800855 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700856
Tao Bao9dd909e2017-11-14 11:27:32 -0800857 For non-AVB images, raise exception if the data is too big. Print a warning
858 if the data is nearing the maximum size.
859
860 For AVB images, the actual image size should be identical to the limit.
861
862 Args:
863 data: A string that contains all the data for the partition.
864 target: The partition name. The ".img" suffix is optional.
865 info_dict: The dict to be looked up for relevant info.
866 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700867 if target.endswith(".img"):
868 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700869 mount_point = "/" + target
870
Ying Wangf8824af2014-06-03 14:07:27 -0700871 fs_type = None
872 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700873 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700874 if mount_point == "/userdata":
875 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700876 p = info_dict["fstab"][mount_point]
877 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800878 device = p.device
879 if "/" in device:
880 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -0800881 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -0700882 if not fs_type or not limit:
883 return
Doug Zongkereef39442009-04-02 12:14:19 -0700884
Andrew Boie0f9aec82012-02-14 09:32:52 -0800885 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800886 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
887 # path.
888 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
889 if size != limit:
890 raise ExternalError(
891 "Mismatching image size for %s: expected %d actual %d" % (
892 target, limit, size))
893 else:
894 pct = float(size) * 100.0 / limit
895 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
896 if pct >= 99.0:
897 raise ExternalError(msg)
898 elif pct >= 95.0:
899 print("\n WARNING: %s\n" % (msg,))
900 elif OPTIONS.verbose:
901 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700902
903
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800904def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800905 """Parses the APK certs info from a given target-files zip.
906
907 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
908 tuple with the following elements: (1) a dictionary that maps packages to
909 certs (based on the "certificate" and "private_key" attributes in the file;
910 (2) a string representing the extension of compressed APKs in the target files
911 (e.g ".gz", ".bro").
912
913 Args:
914 tf_zip: The input target_files ZipFile (already open).
915
916 Returns:
917 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
918 the extension string of compressed APKs (e.g. ".gz"), or None if there's
919 no compressed APKs.
920 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800921 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100922 compressed_extension = None
923
Tao Bao0f990332017-09-08 19:02:54 -0700924 # META/apkcerts.txt contains the info for _all_ the packages known at build
925 # time. Filter out the ones that are not installed.
926 installed_files = set()
927 for name in tf_zip.namelist():
928 basename = os.path.basename(name)
929 if basename:
930 installed_files.add(basename)
931
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800932 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
933 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700934 if not line:
935 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800936 m = re.match(
937 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
938 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
939 line)
940 if not m:
941 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100942
Tao Bao818ddf52018-01-05 11:17:34 -0800943 matches = m.groupdict()
944 cert = matches["CERT"]
945 privkey = matches["PRIVKEY"]
946 name = matches["NAME"]
947 this_compressed_extension = matches["COMPRESSED"]
948
949 public_key_suffix_len = len(OPTIONS.public_key_suffix)
950 private_key_suffix_len = len(OPTIONS.private_key_suffix)
951 if cert in SPECIAL_CERT_STRINGS and not privkey:
952 certmap[name] = cert
953 elif (cert.endswith(OPTIONS.public_key_suffix) and
954 privkey.endswith(OPTIONS.private_key_suffix) and
955 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
956 certmap[name] = cert[:-public_key_suffix_len]
957 else:
958 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
959
960 if not this_compressed_extension:
961 continue
962
963 # Only count the installed files.
964 filename = name + '.' + this_compressed_extension
965 if filename not in installed_files:
966 continue
967
968 # Make sure that all the values in the compression map have the same
969 # extension. We don't support multiple compression methods in the same
970 # system image.
971 if compressed_extension:
972 if this_compressed_extension != compressed_extension:
973 raise ValueError(
974 "Multiple compressed extensions: {} vs {}".format(
975 compressed_extension, this_compressed_extension))
976 else:
977 compressed_extension = this_compressed_extension
978
979 return (certmap,
980 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800981
982
Doug Zongkereef39442009-04-02 12:14:19 -0700983COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -0700984Global options
985
986 -p (--path) <dir>
987 Prepend <dir>/bin to the list of places to search for binaries run by this
988 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700989
Doug Zongker05d3dea2009-06-22 11:32:31 -0700990 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -0700991 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -0700992
Tao Bao30df8b42018-04-23 15:32:53 -0700993 -x (--extra) <key=value>
994 Add a key/value pair to the 'extras' dict, which device-specific extension
995 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -0800996
Doug Zongkereef39442009-04-02 12:14:19 -0700997 -v (--verbose)
998 Show command lines being executed.
999
1000 -h (--help)
1001 Display this usage message and exit.
1002"""
1003
1004def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001005 print(docstring.rstrip("\n"))
1006 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001007
1008
1009def ParseOptions(argv,
1010 docstring,
1011 extra_opts="", extra_long_opts=(),
1012 extra_option_handler=None):
1013 """Parse the options in argv and return any arguments that aren't
1014 flags. docstring is the calling module's docstring, to be displayed
1015 for errors and -h. extra_opts and extra_long_opts are for flags
1016 defined by the caller, which are processed by passing them to
1017 extra_option_handler."""
1018
1019 try:
1020 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001021 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001022 ["help", "verbose", "path=", "signapk_path=",
1023 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001024 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001025 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1026 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001027 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001028 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001029 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001030 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001031 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001032 sys.exit(2)
1033
Doug Zongkereef39442009-04-02 12:14:19 -07001034 for o, a in opts:
1035 if o in ("-h", "--help"):
1036 Usage(docstring)
1037 sys.exit()
1038 elif o in ("-v", "--verbose"):
1039 OPTIONS.verbose = True
1040 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001041 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001042 elif o in ("--signapk_path",):
1043 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001044 elif o in ("--signapk_shared_library_path",):
1045 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001046 elif o in ("--extra_signapk_args",):
1047 OPTIONS.extra_signapk_args = shlex.split(a)
1048 elif o in ("--java_path",):
1049 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001050 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001051 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001052 elif o in ("--public_key_suffix",):
1053 OPTIONS.public_key_suffix = a
1054 elif o in ("--private_key_suffix",):
1055 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001056 elif o in ("--boot_signer_path",):
1057 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001058 elif o in ("--boot_signer_args",):
1059 OPTIONS.boot_signer_args = shlex.split(a)
1060 elif o in ("--verity_signer_path",):
1061 OPTIONS.verity_signer_path = a
1062 elif o in ("--verity_signer_args",):
1063 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001064 elif o in ("-s", "--device_specific"):
1065 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001066 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001067 key, value = a.split("=", 1)
1068 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001069 else:
1070 if extra_option_handler is None or not extra_option_handler(o, a):
1071 assert False, "unknown option \"%s\"" % (o,)
1072
Doug Zongker85448772014-09-09 14:59:20 -07001073 if OPTIONS.search_path:
1074 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1075 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001076
1077 return args
1078
1079
Tao Bao4c851b12016-09-19 13:54:38 -07001080def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001081 """Make a temp file and add it to the list of things to be deleted
1082 when Cleanup() is called. Return the filename."""
1083 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1084 os.close(fd)
1085 OPTIONS.tempfiles.append(fn)
1086 return fn
1087
1088
Tao Bao1c830bf2017-12-25 10:43:47 -08001089def MakeTempDir(prefix='tmp', suffix=''):
1090 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1091
1092 Returns:
1093 The absolute pathname of the new directory.
1094 """
1095 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1096 OPTIONS.tempfiles.append(dir_name)
1097 return dir_name
1098
1099
Doug Zongkereef39442009-04-02 12:14:19 -07001100def Cleanup():
1101 for i in OPTIONS.tempfiles:
1102 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001103 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001104 else:
1105 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001106 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001107
1108
1109class PasswordManager(object):
1110 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001111 self.editor = os.getenv("EDITOR")
1112 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001113
1114 def GetPasswords(self, items):
1115 """Get passwords corresponding to each string in 'items',
1116 returning a dict. (The dict may have keys in addition to the
1117 values in 'items'.)
1118
1119 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1120 user edit that file to add more needed passwords. If no editor is
1121 available, or $ANDROID_PW_FILE isn't define, prompts the user
1122 interactively in the ordinary way.
1123 """
1124
1125 current = self.ReadFile()
1126
1127 first = True
1128 while True:
1129 missing = []
1130 for i in items:
1131 if i not in current or not current[i]:
1132 missing.append(i)
1133 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001134 if not missing:
1135 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001136
1137 for i in missing:
1138 current[i] = ""
1139
1140 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001141 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001142 answer = raw_input("try to edit again? [y]> ").strip()
1143 if answer and answer[0] not in 'yY':
1144 raise RuntimeError("key passwords unavailable")
1145 first = False
1146
1147 current = self.UpdateAndReadFile(current)
1148
Dan Albert8b72aef2015-03-23 19:13:21 -07001149 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001150 """Prompt the user to enter a value (password) for each key in
1151 'current' whose value is fales. Returns a new dict with all the
1152 values.
1153 """
1154 result = {}
1155 for k, v in sorted(current.iteritems()):
1156 if v:
1157 result[k] = v
1158 else:
1159 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001160 result[k] = getpass.getpass(
1161 "Enter password for %s key> " % k).strip()
1162 if result[k]:
1163 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001164 return result
1165
1166 def UpdateAndReadFile(self, current):
1167 if not self.editor or not self.pwfile:
1168 return self.PromptResult(current)
1169
1170 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001171 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001172 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1173 f.write("# (Additional spaces are harmless.)\n\n")
1174
1175 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001176 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1177 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001178 f.write("[[[ %s ]]] %s\n" % (v, k))
1179 if not v and first_line is None:
1180 # position cursor on first line with no password.
1181 first_line = i + 4
1182 f.close()
1183
1184 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1185 _, _ = p.communicate()
1186
1187 return self.ReadFile()
1188
1189 def ReadFile(self):
1190 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001191 if self.pwfile is None:
1192 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001193 try:
1194 f = open(self.pwfile, "r")
1195 for line in f:
1196 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001197 if not line or line[0] == '#':
1198 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001199 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1200 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001201 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001202 else:
1203 result[m.group(2)] = m.group(1)
1204 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001205 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001206 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001207 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001208 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001209
1210
Dan Albert8e0178d2015-01-27 15:53:15 -08001211def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1212 compress_type=None):
1213 import datetime
1214
1215 # http://b/18015246
1216 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1217 # for files larger than 2GiB. We can work around this by adjusting their
1218 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1219 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1220 # it isn't clear to me exactly what circumstances cause this).
1221 # `zipfile.write()` must be used directly to work around this.
1222 #
1223 # This mess can be avoided if we port to python3.
1224 saved_zip64_limit = zipfile.ZIP64_LIMIT
1225 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1226
1227 if compress_type is None:
1228 compress_type = zip_file.compression
1229 if arcname is None:
1230 arcname = filename
1231
1232 saved_stat = os.stat(filename)
1233
1234 try:
1235 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1236 # file to be zipped and reset it when we're done.
1237 os.chmod(filename, perms)
1238
1239 # Use a fixed timestamp so the output is repeatable.
1240 epoch = datetime.datetime.fromtimestamp(0)
1241 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1242 os.utime(filename, (timestamp, timestamp))
1243
1244 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1245 finally:
1246 os.chmod(filename, saved_stat.st_mode)
1247 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1248 zipfile.ZIP64_LIMIT = saved_zip64_limit
1249
1250
Tao Bao58c1b962015-05-20 09:32:18 -07001251def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001252 compress_type=None):
1253 """Wrap zipfile.writestr() function to work around the zip64 limit.
1254
1255 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1256 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1257 when calling crc32(bytes).
1258
1259 But it still works fine to write a shorter string into a large zip file.
1260 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1261 when we know the string won't be too long.
1262 """
1263
1264 saved_zip64_limit = zipfile.ZIP64_LIMIT
1265 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1266
1267 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1268 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001269 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001270 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001271 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001272 else:
Tao Baof3282b42015-04-01 11:21:55 -07001273 zinfo = zinfo_or_arcname
1274
1275 # If compress_type is given, it overrides the value in zinfo.
1276 if compress_type is not None:
1277 zinfo.compress_type = compress_type
1278
Tao Bao58c1b962015-05-20 09:32:18 -07001279 # If perms is given, it has a priority.
1280 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001281 # If perms doesn't set the file type, mark it as a regular file.
1282 if perms & 0o770000 == 0:
1283 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001284 zinfo.external_attr = perms << 16
1285
Tao Baof3282b42015-04-01 11:21:55 -07001286 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001287 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1288
Dan Albert8b72aef2015-03-23 19:13:21 -07001289 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001290 zipfile.ZIP64_LIMIT = saved_zip64_limit
1291
1292
Tao Bao89d7ab22017-12-14 17:05:33 -08001293def ZipDelete(zip_filename, entries):
1294 """Deletes entries from a ZIP file.
1295
1296 Since deleting entries from a ZIP file is not supported, it shells out to
1297 'zip -d'.
1298
1299 Args:
1300 zip_filename: The name of the ZIP file.
1301 entries: The name of the entry, or the list of names to be deleted.
1302
1303 Raises:
1304 AssertionError: In case of non-zero return from 'zip'.
1305 """
1306 if isinstance(entries, basestring):
1307 entries = [entries]
1308 cmd = ["zip", "-d", zip_filename] + entries
1309 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1310 stdoutdata, _ = proc.communicate()
1311 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1312 stdoutdata)
1313
1314
Tao Baof3282b42015-04-01 11:21:55 -07001315def ZipClose(zip_file):
1316 # http://b/18015246
1317 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1318 # central directory.
1319 saved_zip64_limit = zipfile.ZIP64_LIMIT
1320 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1321
1322 zip_file.close()
1323
1324 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001325
1326
1327class DeviceSpecificParams(object):
1328 module = None
1329 def __init__(self, **kwargs):
1330 """Keyword arguments to the constructor become attributes of this
1331 object, which is passed to all functions in the device-specific
1332 module."""
1333 for k, v in kwargs.iteritems():
1334 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001335 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001336
1337 if self.module is None:
1338 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001339 if not path:
1340 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001341 try:
1342 if os.path.isdir(path):
1343 info = imp.find_module("releasetools", [path])
1344 else:
1345 d, f = os.path.split(path)
1346 b, x = os.path.splitext(f)
1347 if x == ".py":
1348 f = b
1349 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001350 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001351 self.module = imp.load_module("device_specific", *info)
1352 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001353 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001354
1355 def _DoCall(self, function_name, *args, **kwargs):
1356 """Call the named function in the device-specific module, passing
1357 the given args and kwargs. The first argument to the call will be
1358 the DeviceSpecific object itself. If there is no module, or the
1359 module does not define the function, return the value of the
1360 'default' kwarg (which itself defaults to None)."""
1361 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001362 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001363 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1364
1365 def FullOTA_Assertions(self):
1366 """Called after emitting the block of assertions at the top of a
1367 full OTA package. Implementations can add whatever additional
1368 assertions they like."""
1369 return self._DoCall("FullOTA_Assertions")
1370
Doug Zongkere5ff5902012-01-17 10:55:37 -08001371 def FullOTA_InstallBegin(self):
1372 """Called at the start of full OTA installation."""
1373 return self._DoCall("FullOTA_InstallBegin")
1374
Doug Zongker05d3dea2009-06-22 11:32:31 -07001375 def FullOTA_InstallEnd(self):
1376 """Called at the end of full OTA installation; typically this is
1377 used to install the image for the device's baseband processor."""
1378 return self._DoCall("FullOTA_InstallEnd")
1379
1380 def IncrementalOTA_Assertions(self):
1381 """Called after emitting the block of assertions at the top of an
1382 incremental OTA package. Implementations can add whatever
1383 additional assertions they like."""
1384 return self._DoCall("IncrementalOTA_Assertions")
1385
Doug Zongkere5ff5902012-01-17 10:55:37 -08001386 def IncrementalOTA_VerifyBegin(self):
1387 """Called at the start of the verification phase of incremental
1388 OTA installation; additional checks can be placed here to abort
1389 the script before any changes are made."""
1390 return self._DoCall("IncrementalOTA_VerifyBegin")
1391
Doug Zongker05d3dea2009-06-22 11:32:31 -07001392 def IncrementalOTA_VerifyEnd(self):
1393 """Called at the end of the verification phase of incremental OTA
1394 installation; additional checks can be placed here to abort the
1395 script before any changes are made."""
1396 return self._DoCall("IncrementalOTA_VerifyEnd")
1397
Doug Zongkere5ff5902012-01-17 10:55:37 -08001398 def IncrementalOTA_InstallBegin(self):
1399 """Called at the start of incremental OTA installation (after
1400 verification is complete)."""
1401 return self._DoCall("IncrementalOTA_InstallBegin")
1402
Doug Zongker05d3dea2009-06-22 11:32:31 -07001403 def IncrementalOTA_InstallEnd(self):
1404 """Called at the end of incremental OTA installation; typically
1405 this is used to install the image for the device's baseband
1406 processor."""
1407 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001408
Tao Bao9bc6bb22015-11-09 16:58:28 -08001409 def VerifyOTA_Assertions(self):
1410 return self._DoCall("VerifyOTA_Assertions")
1411
Tao Bao76def242017-11-21 09:25:31 -08001412
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001413class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001414 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001415 self.name = name
1416 self.data = data
1417 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001418 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001419 self.sha1 = sha1(data).hexdigest()
1420
1421 @classmethod
1422 def FromLocalFile(cls, name, diskname):
1423 f = open(diskname, "rb")
1424 data = f.read()
1425 f.close()
1426 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001427
1428 def WriteToTemp(self):
1429 t = tempfile.NamedTemporaryFile()
1430 t.write(self.data)
1431 t.flush()
1432 return t
1433
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001434 def WriteToDir(self, d):
1435 with open(os.path.join(d, self.name), "wb") as fp:
1436 fp.write(self.data)
1437
Geremy Condra36bd3652014-02-06 19:45:10 -08001438 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001439 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001440
Tao Bao76def242017-11-21 09:25:31 -08001441
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001442DIFF_PROGRAM_BY_EXT = {
1443 ".gz" : "imgdiff",
1444 ".zip" : ["imgdiff", "-z"],
1445 ".jar" : ["imgdiff", "-z"],
1446 ".apk" : ["imgdiff", "-z"],
1447 ".img" : "imgdiff",
1448 }
1449
Tao Bao76def242017-11-21 09:25:31 -08001450
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001451class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001452 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001453 self.tf = tf
1454 self.sf = sf
1455 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001456 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001457
1458 def ComputePatch(self):
1459 """Compute the patch (as a string of data) needed to turn sf into
1460 tf. Returns the same tuple as GetPatch()."""
1461
1462 tf = self.tf
1463 sf = self.sf
1464
Doug Zongker24cd2802012-08-14 16:36:15 -07001465 if self.diff_program:
1466 diff_program = self.diff_program
1467 else:
1468 ext = os.path.splitext(tf.name)[1]
1469 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001470
1471 ttemp = tf.WriteToTemp()
1472 stemp = sf.WriteToTemp()
1473
1474 ext = os.path.splitext(tf.name)[1]
1475
1476 try:
1477 ptemp = tempfile.NamedTemporaryFile()
1478 if isinstance(diff_program, list):
1479 cmd = copy.copy(diff_program)
1480 else:
1481 cmd = [diff_program]
1482 cmd.append(stemp.name)
1483 cmd.append(ttemp.name)
1484 cmd.append(ptemp.name)
1485 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001486 err = []
1487 def run():
1488 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001489 if e:
1490 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001491 th = threading.Thread(target=run)
1492 th.start()
1493 th.join(timeout=300) # 5 mins
1494 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001495 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001496 p.terminate()
1497 th.join(5)
1498 if th.is_alive():
1499 p.kill()
1500 th.join()
1501
Tianjie Xua2a9f992018-01-05 15:15:54 -08001502 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001503 print("WARNING: failure running %s:\n%s\n" % (
1504 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001505 self.patch = None
1506 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001507 diff = ptemp.read()
1508 finally:
1509 ptemp.close()
1510 stemp.close()
1511 ttemp.close()
1512
1513 self.patch = diff
1514 return self.tf, self.sf, self.patch
1515
1516
1517 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001518 """Returns a tuple of (target_file, source_file, patch_data).
1519
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001520 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001521 computing the patch failed.
1522 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001523 return self.tf, self.sf, self.patch
1524
1525
1526def ComputeDifferences(diffs):
1527 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001528 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001529
1530 # Do the largest files first, to try and reduce the long-pole effect.
1531 by_size = [(i.tf.size, i) for i in diffs]
1532 by_size.sort(reverse=True)
1533 by_size = [i[1] for i in by_size]
1534
1535 lock = threading.Lock()
1536 diff_iter = iter(by_size) # accessed under lock
1537
1538 def worker():
1539 try:
1540 lock.acquire()
1541 for d in diff_iter:
1542 lock.release()
1543 start = time.time()
1544 d.ComputePatch()
1545 dur = time.time() - start
1546 lock.acquire()
1547
1548 tf, sf, patch = d.GetPatch()
1549 if sf.name == tf.name:
1550 name = tf.name
1551 else:
1552 name = "%s (%s)" % (tf.name, sf.name)
1553 if patch is None:
Tao Bao76def242017-11-21 09:25:31 -08001554 print(
1555 "patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001556 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001557 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1558 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001559 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001560 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001561 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001562 raise
1563
1564 # start worker threads; wait for them all to finish.
1565 threads = [threading.Thread(target=worker)
1566 for i in range(OPTIONS.worker_threads)]
1567 for th in threads:
1568 th.start()
1569 while threads:
1570 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001571
1572
Dan Albert8b72aef2015-03-23 19:13:21 -07001573class BlockDifference(object):
1574 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001575 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001576 self.tgt = tgt
1577 self.src = src
1578 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001579 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001580 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001581
Tao Baodd2a5892015-03-12 12:32:37 -07001582 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001583 version = max(
1584 int(i) for i in
1585 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001586 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001587 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001588
1589 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001590 version=self.version,
1591 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001592 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001593 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001594 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001595 self.touched_src_ranges = b.touched_src_ranges
1596 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001597
Tao Baoaac4ad52015-10-16 15:26:34 -07001598 if src is None:
1599 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1600 else:
1601 _, self.device = GetTypeAndDevice("/" + partition,
1602 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001603
Tao Baod8d14be2016-02-04 14:26:02 -08001604 @property
1605 def required_cache(self):
1606 return self._required_cache
1607
Tao Bao76def242017-11-21 09:25:31 -08001608 def WriteScript(self, script, output_zip, progress=None,
1609 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001610 if not self.src:
1611 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001612 script.Print("Patching %s image unconditionally..." % (self.partition,))
1613 else:
1614 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001615
Dan Albert8b72aef2015-03-23 19:13:21 -07001616 if progress:
1617 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001618 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001619
1620 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001621 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001622
Tao Bao9bc6bb22015-11-09 16:58:28 -08001623 def WriteStrictVerifyScript(self, script):
1624 """Verify all the blocks in the care_map, including clobbered blocks.
1625
1626 This differs from the WriteVerifyScript() function: a) it prints different
1627 error messages; b) it doesn't allow half-way updated images to pass the
1628 verification."""
1629
1630 partition = self.partition
1631 script.Print("Verifying %s..." % (partition,))
1632 ranges = self.tgt.care_map
1633 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001634 script.AppendExtra(
1635 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1636 'ui_print("\\"%s\\" has unexpected contents.");' % (
1637 self.device, ranges_str,
1638 self.tgt.TotalSha1(include_clobbered_blocks=True),
1639 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001640 script.AppendExtra("")
1641
Tao Baod522bdc2016-04-12 15:53:16 -07001642 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001643 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001644
1645 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001646 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001647 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001648
1649 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001650 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001651 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001652 ranges = self.touched_src_ranges
1653 expected_sha1 = self.touched_src_sha1
1654 else:
1655 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1656 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001657
1658 # No blocks to be checked, skipping.
1659 if not ranges:
1660 return
1661
Tao Bao5ece99d2015-05-12 11:42:31 -07001662 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001663 script.AppendExtra(
1664 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1665 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1666 '"%s.patch.dat")) then' % (
1667 self.device, ranges_str, expected_sha1,
1668 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001669 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001670 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001671
Tianjie Xufc3422a2015-12-15 11:53:59 -08001672 if self.version >= 4:
1673
1674 # Bug: 21124327
1675 # When generating incrementals for the system and vendor partitions in
1676 # version 4 or newer, explicitly check the first block (which contains
1677 # the superblock) of the partition to see if it's what we expect. If
1678 # this check fails, give an explicit log message about the partition
1679 # having been remounted R/W (the most likely explanation).
1680 if self.check_first_block:
1681 script.AppendExtra('check_first_block("%s");' % (self.device,))
1682
1683 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001684 if partition == "system":
1685 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1686 else:
1687 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001688 script.AppendExtra((
1689 'ifelse (block_image_recover("{device}", "{ranges}") && '
1690 'block_image_verify("{device}", '
1691 'package_extract_file("{partition}.transfer.list"), '
1692 '"{partition}.new.dat", "{partition}.patch.dat"), '
1693 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001694 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001695 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001696 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001697
Tao Baodd2a5892015-03-12 12:32:37 -07001698 # Abort the OTA update. Note that the incremental OTA cannot be applied
1699 # even if it may match the checksum of the target partition.
1700 # a) If version < 3, operations like move and erase will make changes
1701 # unconditionally and damage the partition.
1702 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001703 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001704 if partition == "system":
1705 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1706 else:
1707 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1708 script.AppendExtra((
1709 'abort("E%d: %s partition has unexpected contents");\n'
1710 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001711
Tao Bao5fcaaef2015-06-01 13:40:49 -07001712 def _WritePostInstallVerifyScript(self, script):
1713 partition = self.partition
1714 script.Print('Verifying the updated %s image...' % (partition,))
1715 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1716 ranges = self.tgt.care_map
1717 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001718 script.AppendExtra(
1719 'if range_sha1("%s", "%s") == "%s" then' % (
1720 self.device, ranges_str,
1721 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001722
1723 # Bug: 20881595
1724 # Verify that extended blocks are really zeroed out.
1725 if self.tgt.extended:
1726 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001727 script.AppendExtra(
1728 'if range_sha1("%s", "%s") == "%s" then' % (
1729 self.device, ranges_str,
1730 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001731 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001732 if partition == "system":
1733 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1734 else:
1735 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001736 script.AppendExtra(
1737 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001738 ' abort("E%d: %s partition has unexpected non-zero contents after '
1739 'OTA update");\n'
1740 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001741 else:
1742 script.Print('Verified the updated %s image.' % (partition,))
1743
Tianjie Xu209db462016-05-24 17:34:52 -07001744 if partition == "system":
1745 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1746 else:
1747 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1748
Tao Bao5fcaaef2015-06-01 13:40:49 -07001749 script.AppendExtra(
1750 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001751 ' abort("E%d: %s partition has unexpected contents after OTA '
1752 'update");\n'
1753 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001754
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001755 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001756 ZipWrite(output_zip,
1757 '{}.transfer.list'.format(self.path),
1758 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001759
Tao Bao76def242017-11-21 09:25:31 -08001760 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1761 # its size. Quailty 9 almost triples the compression time but doesn't
1762 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001763 # zip | brotli(quality 6) | brotli(quality 9)
1764 # compressed_size: 942M | 869M (~8% reduced) | 854M
1765 # compression_time: 75s | 265s | 719s
1766 # decompression_time: 15s | 25s | 25s
1767
1768 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001769 brotli_cmd = ['brotli', '--quality=6',
1770 '--output={}.new.dat.br'.format(self.path),
1771 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001772 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao80921982018-03-21 21:02:19 -07001773 p = Run(brotli_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1774 stdoutdata, _ = p.communicate()
1775 assert p.returncode == 0, \
1776 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1777 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001778
1779 new_data_name = '{}.new.dat.br'.format(self.partition)
1780 ZipWrite(output_zip,
1781 '{}.new.dat.br'.format(self.path),
1782 new_data_name,
1783 compress_type=zipfile.ZIP_STORED)
1784 else:
1785 new_data_name = '{}.new.dat'.format(self.partition)
1786 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1787
Dan Albert8e0178d2015-01-27 15:53:15 -08001788 ZipWrite(output_zip,
1789 '{}.patch.dat'.format(self.path),
1790 '{}.patch.dat'.format(self.partition),
1791 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001792
Tianjie Xu209db462016-05-24 17:34:52 -07001793 if self.partition == "system":
1794 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1795 else:
1796 code = ErrorCode.VENDOR_UPDATE_FAILURE
1797
Dan Albert8e0178d2015-01-27 15:53:15 -08001798 call = ('block_image_update("{device}", '
1799 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001800 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001801 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001802 device=self.device, partition=self.partition,
1803 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001804 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001805
Dan Albert8b72aef2015-03-23 19:13:21 -07001806 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001807 data = source.ReadRangeSet(ranges)
1808 ctx = sha1()
1809
1810 for p in data:
1811 ctx.update(p)
1812
1813 return ctx.hexdigest()
1814
Tao Baoe9b61912015-07-09 17:37:49 -07001815 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1816 """Return the hash value for all zero blocks."""
1817 zero_block = '\x00' * 4096
1818 ctx = sha1()
1819 for _ in range(num_blocks):
1820 ctx.update(zero_block)
1821
1822 return ctx.hexdigest()
1823
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001824
1825DataImage = blockimgdiff.DataImage
1826
Tao Bao76def242017-11-21 09:25:31 -08001827
Doug Zongker96a57e72010-09-26 14:57:41 -07001828# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001829PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001830 "ext4": "EMMC",
1831 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001832 "f2fs": "EMMC",
1833 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001834}
Doug Zongker96a57e72010-09-26 14:57:41 -07001835
Tao Bao76def242017-11-21 09:25:31 -08001836
Doug Zongker96a57e72010-09-26 14:57:41 -07001837def GetTypeAndDevice(mount_point, info):
1838 fstab = info["fstab"]
1839 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001840 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1841 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001842 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001843 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001844
1845
1846def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001847 """Parses and converts a PEM-encoded certificate into DER-encoded.
1848
1849 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1850
1851 Returns:
1852 The decoded certificate string.
1853 """
1854 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001855 save = False
1856 for line in data.split("\n"):
1857 if "--END CERTIFICATE--" in line:
1858 break
1859 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001860 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001861 if "--BEGIN CERTIFICATE--" in line:
1862 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001863 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001864 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001865
Tao Bao04e1f012018-02-04 12:13:35 -08001866
1867def ExtractPublicKey(cert):
1868 """Extracts the public key (PEM-encoded) from the given certificate file.
1869
1870 Args:
1871 cert: The certificate filename.
1872
1873 Returns:
1874 The public key string.
1875
1876 Raises:
1877 AssertionError: On non-zero return from 'openssl'.
1878 """
1879 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1880 # While openssl 1.1 writes the key into the given filename followed by '-out',
1881 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1882 # stdout instead.
1883 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1884 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1885 pubkey, stderrdata = proc.communicate()
1886 assert proc.returncode == 0, \
1887 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1888 return pubkey
1889
1890
Doug Zongker412c02f2014-02-13 10:58:24 -08001891def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1892 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001893 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001894
Tao Bao6d5d6232018-03-09 17:04:42 -08001895 Most of the space in the boot and recovery images is just the kernel, which is
1896 identical for the two, so the resulting patch should be efficient. Add it to
1897 the output zip, along with a shell script that is run from init.rc on first
1898 boot to actually do the patching and install the new recovery image.
1899
1900 Args:
1901 input_dir: The top-level input directory of the target-files.zip.
1902 output_sink: The callback function that writes the result.
1903 recovery_img: File object for the recovery image.
1904 boot_img: File objects for the boot image.
1905 info_dict: A dict returned by common.LoadInfoDict() on the input
1906 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001907 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001908 if info_dict is None:
1909 info_dict = OPTIONS.info_dict
1910
Tao Bao6d5d6232018-03-09 17:04:42 -08001911 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001912
Tao Baof2cffbd2015-07-22 12:33:18 -07001913 if full_recovery_image:
1914 output_sink("etc/recovery.img", recovery_img.data)
1915
1916 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08001917 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07001918 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08001919 # With system-root-image, boot and recovery images will have mismatching
1920 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
1921 # to handle such a case.
1922 if system_root_image:
1923 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07001924 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08001925 assert not os.path.exists(path)
1926 else:
1927 diff_program = ["imgdiff"]
1928 if os.path.exists(path):
1929 diff_program.append("-b")
1930 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07001931 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08001932 else:
1933 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07001934
1935 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1936 _, _, patch = d.ComputePatch()
1937 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001938
Dan Albertebb19aa2015-03-27 19:11:53 -07001939 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001940 # The following GetTypeAndDevice()s need to use the path in the target
1941 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001942 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1943 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1944 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001945 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001946
Tao Baof2cffbd2015-07-22 12:33:18 -07001947 if full_recovery_image:
1948 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07001949if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
1950 applypatch \\
1951 --flash /system/etc/recovery.img \\
1952 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
1953 log -t recovery "Installing new recovery image: succeeded" || \\
1954 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07001955else
1956 log -t recovery "Recovery image already installed"
1957fi
1958""" % {'type': recovery_type,
1959 'device': recovery_device,
1960 'sha1': recovery_img.sha1,
1961 'size': recovery_img.size}
1962 else:
1963 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07001964if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1965 applypatch %(bonus_args)s \\
1966 --patch /system/recovery-from-boot.p \\
1967 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
1968 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
1969 log -t recovery "Installing new recovery image: succeeded" || \\
1970 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08001971else
1972 log -t recovery "Recovery image already installed"
1973fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001974""" % {'boot_size': boot_img.size,
1975 'boot_sha1': boot_img.sha1,
1976 'recovery_size': recovery_img.size,
1977 'recovery_sha1': recovery_img.sha1,
1978 'boot_type': boot_type,
1979 'boot_device': boot_device,
1980 'recovery_type': recovery_type,
1981 'recovery_device': recovery_device,
1982 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001983
1984 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001985 # in the L release.
1986 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001987
Tao Bao89fbb0f2017-01-10 10:47:58 -08001988 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001989
1990 output_sink(sh_location, sh)