blob: 59b526c58137e9cf1bd07a165fa7fc0926e12317 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Doug Zongkerea5d7a92010-09-12 15:26:16 -070017import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070018import errno
Doug Zongkereef39442009-04-02 12:14:19 -070019import getopt
20import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010021import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070022import imp
Doug Zongkereef39442009-04-02 12:14:19 -070023import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080024import platform
Doug Zongkereef39442009-04-02 12:14:19 -070025import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070026import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070027import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080028import string
Doug Zongkereef39442009-04-02 12:14:19 -070029import subprocess
30import sys
31import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070032import threading
33import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070034import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080035from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070036
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070037import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080038import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070039
Dan Albert8b72aef2015-03-23 19:13:21 -070040class Options(object):
41 def __init__(self):
42 platform_search_path = {
43 "linux2": "out/host/linux-x86",
44 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070045 }
Doug Zongker85448772014-09-09 14:59:20 -070046
Dan Albert8b72aef2015-03-23 19:13:21 -070047 self.search_path = platform_search_path.get(sys.platform, None)
48 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080049 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070050 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080052 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070053 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070055 # use otatools built boot_signer by default
56 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070057 self.boot_signer_args = []
58 self.verity_signer_path = None
59 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.verbose = False
61 self.tempfiles = []
62 self.device_specific = None
63 self.extras = {}
64 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070065 self.source_info_dict = None
66 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070068 # Stash size cannot exceed cache_size * threshold.
69 self.cache_size = None
70 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070071
72
73OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070074
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080075
76# Values for "certificate" in apkcerts that mean special things.
77SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
78
Tao Bao9dd909e2017-11-14 11:27:32 -080079
80# The partitions allowed to be signed by AVB (Android verified boot 2.0).
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
Tianjie Xucfa86222016-03-07 16:31:19 -0800240 system_root_image = d.get("system_root_image", None) == "true"
241 if d.get("no_recovery", None) != "true":
242 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800243 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800244 recovery_fstab_path, system_root_image)
245 elif d.get("recovery_as_boot", None) == "true":
246 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
247 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
248 recovery_fstab_path, system_root_image)
249 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
Doug Zongkerd5131602012-08-02 14:46:42 -0700444 args = info_dict.get("mkbootimg_args", None)
445 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
Sami Tolvanen3303d902016-03-15 16:49:30 +0000448 args = info_dict.get("mkbootimg_version_args", None)
449 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
456 if info_dict.get("vboot", None):
457 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 Baobf70c312017-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 Baobf70c312017-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
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100474 if (info_dict.get("boot_signer", None) == "true" and
475 info_dict.get("verity_key", None)):
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 Baobf70c312017-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.
492 elif info_dict.get("vboot", None):
Tao Baobf70c312017-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 Baobf70c312017-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 Baobf70c312017-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):
581 """Gunzip the given gzip compressed file to a given output file.
582 """
583 with gzip.open(in_filename, "rb") as in_file, open(out_filename, "wb") as out_file:
584 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))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700737 key_passwords.update(dict.fromkeys(no_passwords, None))
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,
802 codename_to_api_level_map=dict(),
803 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700804 """Sign the input_name zip/jar/apk, producing output_name. Use the
805 given key and password (the latter may be None if the key does not
806 have a password.
807
Doug Zongker951495f2009-08-14 12:44:19 -0700808 If whole_file is true, use the "-w" option to SignApk to embed a
809 signature that covers the whole file in the archive comment of the
810 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800811
812 min_api_level is the API Level (int) of the oldest platform this file may end
813 up on. If not specified for an APK, the API Level is obtained by interpreting
814 the minSdkVersion attribute of the APK's AndroidManifest.xml.
815
816 codename_to_api_level_map is needed to translate the codename which may be
817 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700818 """
Doug Zongker951495f2009-08-14 12:44:19 -0700819
Alex Klyubin9667b182015-12-10 13:38:50 -0800820 java_library_path = os.path.join(
821 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
822
Tao Baoe95540e2016-11-08 12:08:53 -0800823 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
824 ["-Djava.library.path=" + java_library_path,
825 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
826 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700827 if whole_file:
828 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800829
830 min_sdk_version = min_api_level
831 if min_sdk_version is None:
832 if not whole_file:
833 min_sdk_version = GetMinSdkVersionInt(
834 input_name, codename_to_api_level_map)
835 if min_sdk_version is not None:
836 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
837
T.R. Fullhart37e10522013-03-18 10:31:26 -0700838 cmd.extend([key + OPTIONS.public_key_suffix,
839 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800840 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700841
Tao Bao80921982018-03-21 21:02:19 -0700842 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
843 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700844 if password is not None:
845 password += "\n"
Tao Bao80921982018-03-21 21:02:19 -0700846 stdoutdata, _ = p.communicate(password)
Doug Zongkereef39442009-04-02 12:14:19 -0700847 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700848 raise ExternalError(
849 "Failed to run signapk.jar: return code {}:\n{}".format(
850 p.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700851
Doug Zongkereef39442009-04-02 12:14:19 -0700852
Doug Zongker37974732010-09-16 17:44:38 -0700853def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800854 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700855
Tao Bao9dd909e2017-11-14 11:27:32 -0800856 For non-AVB images, raise exception if the data is too big. Print a warning
857 if the data is nearing the maximum size.
858
859 For AVB images, the actual image size should be identical to the limit.
860
861 Args:
862 data: A string that contains all the data for the partition.
863 target: The partition name. The ".img" suffix is optional.
864 info_dict: The dict to be looked up for relevant info.
865 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700866 if target.endswith(".img"):
867 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700868 mount_point = "/" + target
869
Ying Wangf8824af2014-06-03 14:07:27 -0700870 fs_type = None
871 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700872 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700873 if mount_point == "/userdata":
874 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700875 p = info_dict["fstab"][mount_point]
876 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800877 device = p.device
878 if "/" in device:
879 device = device[device.rfind("/")+1:]
880 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700881 if not fs_type or not limit:
882 return
Doug Zongkereef39442009-04-02 12:14:19 -0700883
Andrew Boie0f9aec82012-02-14 09:32:52 -0800884 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800885 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
886 # path.
887 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
888 if size != limit:
889 raise ExternalError(
890 "Mismatching image size for %s: expected %d actual %d" % (
891 target, limit, size))
892 else:
893 pct = float(size) * 100.0 / limit
894 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
895 if pct >= 99.0:
896 raise ExternalError(msg)
897 elif pct >= 95.0:
898 print("\n WARNING: %s\n" % (msg,))
899 elif OPTIONS.verbose:
900 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700901
902
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800903def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800904 """Parses the APK certs info from a given target-files zip.
905
906 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
907 tuple with the following elements: (1) a dictionary that maps packages to
908 certs (based on the "certificate" and "private_key" attributes in the file;
909 (2) a string representing the extension of compressed APKs in the target files
910 (e.g ".gz", ".bro").
911
912 Args:
913 tf_zip: The input target_files ZipFile (already open).
914
915 Returns:
916 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
917 the extension string of compressed APKs (e.g. ".gz"), or None if there's
918 no compressed APKs.
919 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800920 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100921 compressed_extension = None
922
Tao Bao0f990332017-09-08 19:02:54 -0700923 # META/apkcerts.txt contains the info for _all_ the packages known at build
924 # time. Filter out the ones that are not installed.
925 installed_files = set()
926 for name in tf_zip.namelist():
927 basename = os.path.basename(name)
928 if basename:
929 installed_files.add(basename)
930
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800931 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
932 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700933 if not line:
934 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800935 m = re.match(
936 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
937 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
938 line)
939 if not m:
940 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100941
Tao Bao818ddf52018-01-05 11:17:34 -0800942 matches = m.groupdict()
943 cert = matches["CERT"]
944 privkey = matches["PRIVKEY"]
945 name = matches["NAME"]
946 this_compressed_extension = matches["COMPRESSED"]
947
948 public_key_suffix_len = len(OPTIONS.public_key_suffix)
949 private_key_suffix_len = len(OPTIONS.private_key_suffix)
950 if cert in SPECIAL_CERT_STRINGS and not privkey:
951 certmap[name] = cert
952 elif (cert.endswith(OPTIONS.public_key_suffix) and
953 privkey.endswith(OPTIONS.private_key_suffix) and
954 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
955 certmap[name] = cert[:-public_key_suffix_len]
956 else:
957 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
958
959 if not this_compressed_extension:
960 continue
961
962 # Only count the installed files.
963 filename = name + '.' + this_compressed_extension
964 if filename not in installed_files:
965 continue
966
967 # Make sure that all the values in the compression map have the same
968 # extension. We don't support multiple compression methods in the same
969 # system image.
970 if compressed_extension:
971 if this_compressed_extension != compressed_extension:
972 raise ValueError(
973 "Multiple compressed extensions: {} vs {}".format(
974 compressed_extension, this_compressed_extension))
975 else:
976 compressed_extension = this_compressed_extension
977
978 return (certmap,
979 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800980
981
Doug Zongkereef39442009-04-02 12:14:19 -0700982COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -0700983Global options
984
985 -p (--path) <dir>
986 Prepend <dir>/bin to the list of places to search for binaries run by this
987 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700988
Doug Zongker05d3dea2009-06-22 11:32:31 -0700989 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -0700990 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -0700991
Tao Bao30df8b42018-04-23 15:32:53 -0700992 -x (--extra) <key=value>
993 Add a key/value pair to the 'extras' dict, which device-specific extension
994 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -0800995
Doug Zongkereef39442009-04-02 12:14:19 -0700996 -v (--verbose)
997 Show command lines being executed.
998
999 -h (--help)
1000 Display this usage message and exit.
1001"""
1002
1003def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001004 print(docstring.rstrip("\n"))
1005 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001006
1007
1008def ParseOptions(argv,
1009 docstring,
1010 extra_opts="", extra_long_opts=(),
1011 extra_option_handler=None):
1012 """Parse the options in argv and return any arguments that aren't
1013 flags. docstring is the calling module's docstring, to be displayed
1014 for errors and -h. extra_opts and extra_long_opts are for flags
1015 defined by the caller, which are processed by passing them to
1016 extra_option_handler."""
1017
1018 try:
1019 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001020 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001021 ["help", "verbose", "path=", "signapk_path=",
1022 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001023 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001024 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1025 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001026 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001027 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001028 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001029 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001030 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001031 sys.exit(2)
1032
Doug Zongkereef39442009-04-02 12:14:19 -07001033 for o, a in opts:
1034 if o in ("-h", "--help"):
1035 Usage(docstring)
1036 sys.exit()
1037 elif o in ("-v", "--verbose"):
1038 OPTIONS.verbose = True
1039 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001040 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001041 elif o in ("--signapk_path",):
1042 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001043 elif o in ("--signapk_shared_library_path",):
1044 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001045 elif o in ("--extra_signapk_args",):
1046 OPTIONS.extra_signapk_args = shlex.split(a)
1047 elif o in ("--java_path",):
1048 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001049 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001050 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001051 elif o in ("--public_key_suffix",):
1052 OPTIONS.public_key_suffix = a
1053 elif o in ("--private_key_suffix",):
1054 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001055 elif o in ("--boot_signer_path",):
1056 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001057 elif o in ("--boot_signer_args",):
1058 OPTIONS.boot_signer_args = shlex.split(a)
1059 elif o in ("--verity_signer_path",):
1060 OPTIONS.verity_signer_path = a
1061 elif o in ("--verity_signer_args",):
1062 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001063 elif o in ("-s", "--device_specific"):
1064 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001065 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001066 key, value = a.split("=", 1)
1067 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001068 else:
1069 if extra_option_handler is None or not extra_option_handler(o, a):
1070 assert False, "unknown option \"%s\"" % (o,)
1071
Doug Zongker85448772014-09-09 14:59:20 -07001072 if OPTIONS.search_path:
1073 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1074 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001075
1076 return args
1077
1078
Tao Bao4c851b12016-09-19 13:54:38 -07001079def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001080 """Make a temp file and add it to the list of things to be deleted
1081 when Cleanup() is called. Return the filename."""
1082 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1083 os.close(fd)
1084 OPTIONS.tempfiles.append(fn)
1085 return fn
1086
1087
Tao Bao1c830bf2017-12-25 10:43:47 -08001088def MakeTempDir(prefix='tmp', suffix=''):
1089 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1090
1091 Returns:
1092 The absolute pathname of the new directory.
1093 """
1094 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1095 OPTIONS.tempfiles.append(dir_name)
1096 return dir_name
1097
1098
Doug Zongkereef39442009-04-02 12:14:19 -07001099def Cleanup():
1100 for i in OPTIONS.tempfiles:
1101 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001102 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001103 else:
1104 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001105 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001106
1107
1108class PasswordManager(object):
1109 def __init__(self):
1110 self.editor = os.getenv("EDITOR", None)
1111 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
1112
1113 def GetPasswords(self, items):
1114 """Get passwords corresponding to each string in 'items',
1115 returning a dict. (The dict may have keys in addition to the
1116 values in 'items'.)
1117
1118 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1119 user edit that file to add more needed passwords. If no editor is
1120 available, or $ANDROID_PW_FILE isn't define, prompts the user
1121 interactively in the ordinary way.
1122 """
1123
1124 current = self.ReadFile()
1125
1126 first = True
1127 while True:
1128 missing = []
1129 for i in items:
1130 if i not in current or not current[i]:
1131 missing.append(i)
1132 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001133 if not missing:
1134 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001135
1136 for i in missing:
1137 current[i] = ""
1138
1139 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001140 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001141 answer = raw_input("try to edit again? [y]> ").strip()
1142 if answer and answer[0] not in 'yY':
1143 raise RuntimeError("key passwords unavailable")
1144 first = False
1145
1146 current = self.UpdateAndReadFile(current)
1147
Dan Albert8b72aef2015-03-23 19:13:21 -07001148 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001149 """Prompt the user to enter a value (password) for each key in
1150 'current' whose value is fales. Returns a new dict with all the
1151 values.
1152 """
1153 result = {}
1154 for k, v in sorted(current.iteritems()):
1155 if v:
1156 result[k] = v
1157 else:
1158 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001159 result[k] = getpass.getpass(
1160 "Enter password for %s key> " % k).strip()
1161 if result[k]:
1162 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001163 return result
1164
1165 def UpdateAndReadFile(self, current):
1166 if not self.editor or not self.pwfile:
1167 return self.PromptResult(current)
1168
1169 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001170 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001171 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1172 f.write("# (Additional spaces are harmless.)\n\n")
1173
1174 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001175 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1176 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001177 f.write("[[[ %s ]]] %s\n" % (v, k))
1178 if not v and first_line is None:
1179 # position cursor on first line with no password.
1180 first_line = i + 4
1181 f.close()
1182
1183 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1184 _, _ = p.communicate()
1185
1186 return self.ReadFile()
1187
1188 def ReadFile(self):
1189 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001190 if self.pwfile is None:
1191 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001192 try:
1193 f = open(self.pwfile, "r")
1194 for line in f:
1195 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001196 if not line or line[0] == '#':
1197 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001198 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1199 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001200 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001201 else:
1202 result[m.group(2)] = m.group(1)
1203 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001204 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001205 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001206 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001207 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001208
1209
Dan Albert8e0178d2015-01-27 15:53:15 -08001210def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1211 compress_type=None):
1212 import datetime
1213
1214 # http://b/18015246
1215 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1216 # for files larger than 2GiB. We can work around this by adjusting their
1217 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1218 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1219 # it isn't clear to me exactly what circumstances cause this).
1220 # `zipfile.write()` must be used directly to work around this.
1221 #
1222 # This mess can be avoided if we port to python3.
1223 saved_zip64_limit = zipfile.ZIP64_LIMIT
1224 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1225
1226 if compress_type is None:
1227 compress_type = zip_file.compression
1228 if arcname is None:
1229 arcname = filename
1230
1231 saved_stat = os.stat(filename)
1232
1233 try:
1234 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1235 # file to be zipped and reset it when we're done.
1236 os.chmod(filename, perms)
1237
1238 # Use a fixed timestamp so the output is repeatable.
1239 epoch = datetime.datetime.fromtimestamp(0)
1240 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1241 os.utime(filename, (timestamp, timestamp))
1242
1243 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1244 finally:
1245 os.chmod(filename, saved_stat.st_mode)
1246 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1247 zipfile.ZIP64_LIMIT = saved_zip64_limit
1248
1249
Tao Bao58c1b962015-05-20 09:32:18 -07001250def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001251 compress_type=None):
1252 """Wrap zipfile.writestr() function to work around the zip64 limit.
1253
1254 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1255 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1256 when calling crc32(bytes).
1257
1258 But it still works fine to write a shorter string into a large zip file.
1259 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1260 when we know the string won't be too long.
1261 """
1262
1263 saved_zip64_limit = zipfile.ZIP64_LIMIT
1264 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1265
1266 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1267 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001268 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001269 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001270 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001271 else:
Tao Baof3282b42015-04-01 11:21:55 -07001272 zinfo = zinfo_or_arcname
1273
1274 # If compress_type is given, it overrides the value in zinfo.
1275 if compress_type is not None:
1276 zinfo.compress_type = compress_type
1277
Tao Bao58c1b962015-05-20 09:32:18 -07001278 # If perms is given, it has a priority.
1279 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001280 # If perms doesn't set the file type, mark it as a regular file.
1281 if perms & 0o770000 == 0:
1282 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001283 zinfo.external_attr = perms << 16
1284
Tao Baof3282b42015-04-01 11:21:55 -07001285 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001286 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1287
Dan Albert8b72aef2015-03-23 19:13:21 -07001288 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001289 zipfile.ZIP64_LIMIT = saved_zip64_limit
1290
1291
Tao Bao89d7ab22017-12-14 17:05:33 -08001292def ZipDelete(zip_filename, entries):
1293 """Deletes entries from a ZIP file.
1294
1295 Since deleting entries from a ZIP file is not supported, it shells out to
1296 'zip -d'.
1297
1298 Args:
1299 zip_filename: The name of the ZIP file.
1300 entries: The name of the entry, or the list of names to be deleted.
1301
1302 Raises:
1303 AssertionError: In case of non-zero return from 'zip'.
1304 """
1305 if isinstance(entries, basestring):
1306 entries = [entries]
1307 cmd = ["zip", "-d", zip_filename] + entries
1308 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1309 stdoutdata, _ = proc.communicate()
1310 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1311 stdoutdata)
1312
1313
Tao Baof3282b42015-04-01 11:21:55 -07001314def ZipClose(zip_file):
1315 # http://b/18015246
1316 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1317 # central directory.
1318 saved_zip64_limit = zipfile.ZIP64_LIMIT
1319 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1320
1321 zip_file.close()
1322
1323 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001324
1325
1326class DeviceSpecificParams(object):
1327 module = None
1328 def __init__(self, **kwargs):
1329 """Keyword arguments to the constructor become attributes of this
1330 object, which is passed to all functions in the device-specific
1331 module."""
1332 for k, v in kwargs.iteritems():
1333 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001334 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001335
1336 if self.module is None:
1337 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001338 if not path:
1339 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001340 try:
1341 if os.path.isdir(path):
1342 info = imp.find_module("releasetools", [path])
1343 else:
1344 d, f = os.path.split(path)
1345 b, x = os.path.splitext(f)
1346 if x == ".py":
1347 f = b
1348 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001349 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001350 self.module = imp.load_module("device_specific", *info)
1351 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001352 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001353
1354 def _DoCall(self, function_name, *args, **kwargs):
1355 """Call the named function in the device-specific module, passing
1356 the given args and kwargs. The first argument to the call will be
1357 the DeviceSpecific object itself. If there is no module, or the
1358 module does not define the function, return the value of the
1359 'default' kwarg (which itself defaults to None)."""
1360 if self.module is None or not hasattr(self.module, function_name):
1361 return kwargs.get("default", None)
1362 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1363
1364 def FullOTA_Assertions(self):
1365 """Called after emitting the block of assertions at the top of a
1366 full OTA package. Implementations can add whatever additional
1367 assertions they like."""
1368 return self._DoCall("FullOTA_Assertions")
1369
Doug Zongkere5ff5902012-01-17 10:55:37 -08001370 def FullOTA_InstallBegin(self):
1371 """Called at the start of full OTA installation."""
1372 return self._DoCall("FullOTA_InstallBegin")
1373
Doug Zongker05d3dea2009-06-22 11:32:31 -07001374 def FullOTA_InstallEnd(self):
1375 """Called at the end of full OTA installation; typically this is
1376 used to install the image for the device's baseband processor."""
1377 return self._DoCall("FullOTA_InstallEnd")
1378
1379 def IncrementalOTA_Assertions(self):
1380 """Called after emitting the block of assertions at the top of an
1381 incremental OTA package. Implementations can add whatever
1382 additional assertions they like."""
1383 return self._DoCall("IncrementalOTA_Assertions")
1384
Doug Zongkere5ff5902012-01-17 10:55:37 -08001385 def IncrementalOTA_VerifyBegin(self):
1386 """Called at the start of the verification phase of incremental
1387 OTA installation; additional checks can be placed here to abort
1388 the script before any changes are made."""
1389 return self._DoCall("IncrementalOTA_VerifyBegin")
1390
Doug Zongker05d3dea2009-06-22 11:32:31 -07001391 def IncrementalOTA_VerifyEnd(self):
1392 """Called at the end of the verification phase of incremental OTA
1393 installation; additional checks can be placed here to abort the
1394 script before any changes are made."""
1395 return self._DoCall("IncrementalOTA_VerifyEnd")
1396
Doug Zongkere5ff5902012-01-17 10:55:37 -08001397 def IncrementalOTA_InstallBegin(self):
1398 """Called at the start of incremental OTA installation (after
1399 verification is complete)."""
1400 return self._DoCall("IncrementalOTA_InstallBegin")
1401
Doug Zongker05d3dea2009-06-22 11:32:31 -07001402 def IncrementalOTA_InstallEnd(self):
1403 """Called at the end of incremental OTA installation; typically
1404 this is used to install the image for the device's baseband
1405 processor."""
1406 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001407
Tao Bao9bc6bb22015-11-09 16:58:28 -08001408 def VerifyOTA_Assertions(self):
1409 return self._DoCall("VerifyOTA_Assertions")
1410
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001411class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001412 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001413 self.name = name
1414 self.data = data
1415 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001416 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001417 self.sha1 = sha1(data).hexdigest()
1418
1419 @classmethod
1420 def FromLocalFile(cls, name, diskname):
1421 f = open(diskname, "rb")
1422 data = f.read()
1423 f.close()
1424 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001425
1426 def WriteToTemp(self):
1427 t = tempfile.NamedTemporaryFile()
1428 t.write(self.data)
1429 t.flush()
1430 return t
1431
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001432 def WriteToDir(self, d):
1433 with open(os.path.join(d, self.name), "wb") as fp:
1434 fp.write(self.data)
1435
Geremy Condra36bd3652014-02-06 19:45:10 -08001436 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001437 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001438
1439DIFF_PROGRAM_BY_EXT = {
1440 ".gz" : "imgdiff",
1441 ".zip" : ["imgdiff", "-z"],
1442 ".jar" : ["imgdiff", "-z"],
1443 ".apk" : ["imgdiff", "-z"],
1444 ".img" : "imgdiff",
1445 }
1446
1447class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001448 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001449 self.tf = tf
1450 self.sf = sf
1451 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001452 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001453
1454 def ComputePatch(self):
1455 """Compute the patch (as a string of data) needed to turn sf into
1456 tf. Returns the same tuple as GetPatch()."""
1457
1458 tf = self.tf
1459 sf = self.sf
1460
Doug Zongker24cd2802012-08-14 16:36:15 -07001461 if self.diff_program:
1462 diff_program = self.diff_program
1463 else:
1464 ext = os.path.splitext(tf.name)[1]
1465 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001466
1467 ttemp = tf.WriteToTemp()
1468 stemp = sf.WriteToTemp()
1469
1470 ext = os.path.splitext(tf.name)[1]
1471
1472 try:
1473 ptemp = tempfile.NamedTemporaryFile()
1474 if isinstance(diff_program, list):
1475 cmd = copy.copy(diff_program)
1476 else:
1477 cmd = [diff_program]
1478 cmd.append(stemp.name)
1479 cmd.append(ttemp.name)
1480 cmd.append(ptemp.name)
1481 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001482 err = []
1483 def run():
1484 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001485 if e:
1486 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001487 th = threading.Thread(target=run)
1488 th.start()
1489 th.join(timeout=300) # 5 mins
1490 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001491 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001492 p.terminate()
1493 th.join(5)
1494 if th.is_alive():
1495 p.kill()
1496 th.join()
1497
Tianjie Xua2a9f992018-01-05 15:15:54 -08001498 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001499 print("WARNING: failure running %s:\n%s\n" % (
1500 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001501 self.patch = None
1502 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001503 diff = ptemp.read()
1504 finally:
1505 ptemp.close()
1506 stemp.close()
1507 ttemp.close()
1508
1509 self.patch = diff
1510 return self.tf, self.sf, self.patch
1511
1512
1513 def GetPatch(self):
1514 """Return a tuple (target_file, source_file, patch_data).
1515 patch_data may be None if ComputePatch hasn't been called, or if
1516 computing the patch failed."""
1517 return self.tf, self.sf, self.patch
1518
1519
1520def ComputeDifferences(diffs):
1521 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001522 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001523
1524 # Do the largest files first, to try and reduce the long-pole effect.
1525 by_size = [(i.tf.size, i) for i in diffs]
1526 by_size.sort(reverse=True)
1527 by_size = [i[1] for i in by_size]
1528
1529 lock = threading.Lock()
1530 diff_iter = iter(by_size) # accessed under lock
1531
1532 def worker():
1533 try:
1534 lock.acquire()
1535 for d in diff_iter:
1536 lock.release()
1537 start = time.time()
1538 d.ComputePatch()
1539 dur = time.time() - start
1540 lock.acquire()
1541
1542 tf, sf, patch = d.GetPatch()
1543 if sf.name == tf.name:
1544 name = tf.name
1545 else:
1546 name = "%s (%s)" % (tf.name, sf.name)
1547 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001548 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001549 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001550 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1551 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001552 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001553 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001554 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001555 raise
1556
1557 # start worker threads; wait for them all to finish.
1558 threads = [threading.Thread(target=worker)
1559 for i in range(OPTIONS.worker_threads)]
1560 for th in threads:
1561 th.start()
1562 while threads:
1563 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001564
1565
Dan Albert8b72aef2015-03-23 19:13:21 -07001566class BlockDifference(object):
1567 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001568 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001569 self.tgt = tgt
1570 self.src = src
1571 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001572 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001573 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001574
Tao Baodd2a5892015-03-12 12:32:37 -07001575 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001576 version = max(
1577 int(i) for i in
1578 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001579 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001580 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001581
1582 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001583 version=self.version,
1584 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001585 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001586 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001587 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001588 self.touched_src_ranges = b.touched_src_ranges
1589 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001590
Tao Baoaac4ad52015-10-16 15:26:34 -07001591 if src is None:
1592 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1593 else:
1594 _, self.device = GetTypeAndDevice("/" + partition,
1595 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001596
Tao Baod8d14be2016-02-04 14:26:02 -08001597 @property
1598 def required_cache(self):
1599 return self._required_cache
1600
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001601 def WriteScript(self, script, output_zip, progress=None):
1602 if not self.src:
1603 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001604 script.Print("Patching %s image unconditionally..." % (self.partition,))
1605 else:
1606 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001607
Dan Albert8b72aef2015-03-23 19:13:21 -07001608 if progress:
1609 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001610 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001611 if OPTIONS.verify:
1612 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001613
Tao Bao9bc6bb22015-11-09 16:58:28 -08001614 def WriteStrictVerifyScript(self, script):
1615 """Verify all the blocks in the care_map, including clobbered blocks.
1616
1617 This differs from the WriteVerifyScript() function: a) it prints different
1618 error messages; b) it doesn't allow half-way updated images to pass the
1619 verification."""
1620
1621 partition = self.partition
1622 script.Print("Verifying %s..." % (partition,))
1623 ranges = self.tgt.care_map
1624 ranges_str = ranges.to_string_raw()
1625 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1626 'ui_print(" Verified.") || '
1627 'ui_print("\\"%s\\" has unexpected contents.");' % (
1628 self.device, ranges_str,
1629 self.tgt.TotalSha1(include_clobbered_blocks=True),
1630 self.device))
1631 script.AppendExtra("")
1632
Tao Baod522bdc2016-04-12 15:53:16 -07001633 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001634 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001635
1636 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001637 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001638 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001639
1640 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001641 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001642 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001643 ranges = self.touched_src_ranges
1644 expected_sha1 = self.touched_src_sha1
1645 else:
1646 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1647 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001648
1649 # No blocks to be checked, skipping.
1650 if not ranges:
1651 return
1652
Tao Bao5ece99d2015-05-12 11:42:31 -07001653 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001654 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1655 'block_image_verify("%s", '
1656 'package_extract_file("%s.transfer.list"), '
1657 '"%s.new.dat", "%s.patch.dat")) then') % (
1658 self.device, ranges_str, expected_sha1,
1659 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001660 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001661 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001662
Tianjie Xufc3422a2015-12-15 11:53:59 -08001663 if self.version >= 4:
1664
1665 # Bug: 21124327
1666 # When generating incrementals for the system and vendor partitions in
1667 # version 4 or newer, explicitly check the first block (which contains
1668 # the superblock) of the partition to see if it's what we expect. If
1669 # this check fails, give an explicit log message about the partition
1670 # having been remounted R/W (the most likely explanation).
1671 if self.check_first_block:
1672 script.AppendExtra('check_first_block("%s");' % (self.device,))
1673
1674 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001675 if partition == "system":
1676 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1677 else:
1678 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001679 script.AppendExtra((
1680 'ifelse (block_image_recover("{device}", "{ranges}") && '
1681 'block_image_verify("{device}", '
1682 'package_extract_file("{partition}.transfer.list"), '
1683 '"{partition}.new.dat", "{partition}.patch.dat"), '
1684 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001685 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001686 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001687 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001688
Tao Baodd2a5892015-03-12 12:32:37 -07001689 # Abort the OTA update. Note that the incremental OTA cannot be applied
1690 # even if it may match the checksum of the target partition.
1691 # a) If version < 3, operations like move and erase will make changes
1692 # unconditionally and damage the partition.
1693 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001694 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001695 if partition == "system":
1696 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1697 else:
1698 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1699 script.AppendExtra((
1700 'abort("E%d: %s partition has unexpected contents");\n'
1701 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001702
Tao Bao5fcaaef2015-06-01 13:40:49 -07001703 def _WritePostInstallVerifyScript(self, script):
1704 partition = self.partition
1705 script.Print('Verifying the updated %s image...' % (partition,))
1706 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1707 ranges = self.tgt.care_map
1708 ranges_str = ranges.to_string_raw()
1709 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1710 self.device, ranges_str,
1711 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001712
1713 # Bug: 20881595
1714 # Verify that extended blocks are really zeroed out.
1715 if self.tgt.extended:
1716 ranges_str = self.tgt.extended.to_string_raw()
1717 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1718 self.device, ranges_str,
1719 self._HashZeroBlocks(self.tgt.extended.size())))
1720 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001721 if partition == "system":
1722 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1723 else:
1724 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001725 script.AppendExtra(
1726 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001727 ' abort("E%d: %s partition has unexpected non-zero contents after '
1728 'OTA update");\n'
1729 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001730 else:
1731 script.Print('Verified the updated %s image.' % (partition,))
1732
Tianjie Xu209db462016-05-24 17:34:52 -07001733 if partition == "system":
1734 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1735 else:
1736 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1737
Tao Bao5fcaaef2015-06-01 13:40:49 -07001738 script.AppendExtra(
1739 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001740 ' abort("E%d: %s partition has unexpected contents after OTA '
1741 'update");\n'
1742 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001743
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001744 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001745 ZipWrite(output_zip,
1746 '{}.transfer.list'.format(self.path),
1747 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001748
1749 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1750 # almost triples the compression time but doesn't further reduce the size too much.
1751 # For a typical 1.8G system.new.dat
1752 # zip | brotli(quality 6) | brotli(quality 9)
1753 # compressed_size: 942M | 869M (~8% reduced) | 854M
1754 # compression_time: 75s | 265s | 719s
1755 # decompression_time: 15s | 25s | 25s
1756
1757 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001758 brotli_cmd = ['brotli', '--quality=6',
1759 '--output={}.new.dat.br'.format(self.path),
1760 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001761 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao80921982018-03-21 21:02:19 -07001762 p = Run(brotli_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1763 stdoutdata, _ = p.communicate()
1764 assert p.returncode == 0, \
1765 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1766 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001767
1768 new_data_name = '{}.new.dat.br'.format(self.partition)
1769 ZipWrite(output_zip,
1770 '{}.new.dat.br'.format(self.path),
1771 new_data_name,
1772 compress_type=zipfile.ZIP_STORED)
1773 else:
1774 new_data_name = '{}.new.dat'.format(self.partition)
1775 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1776
Dan Albert8e0178d2015-01-27 15:53:15 -08001777 ZipWrite(output_zip,
1778 '{}.patch.dat'.format(self.path),
1779 '{}.patch.dat'.format(self.partition),
1780 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001781
Tianjie Xu209db462016-05-24 17:34:52 -07001782 if self.partition == "system":
1783 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1784 else:
1785 code = ErrorCode.VENDOR_UPDATE_FAILURE
1786
Dan Albert8e0178d2015-01-27 15:53:15 -08001787 call = ('block_image_update("{device}", '
1788 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001789 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001790 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001791 device=self.device, partition=self.partition,
1792 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001793 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001794
Dan Albert8b72aef2015-03-23 19:13:21 -07001795 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001796 data = source.ReadRangeSet(ranges)
1797 ctx = sha1()
1798
1799 for p in data:
1800 ctx.update(p)
1801
1802 return ctx.hexdigest()
1803
Tao Baoe9b61912015-07-09 17:37:49 -07001804 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1805 """Return the hash value for all zero blocks."""
1806 zero_block = '\x00' * 4096
1807 ctx = sha1()
1808 for _ in range(num_blocks):
1809 ctx.update(zero_block)
1810
1811 return ctx.hexdigest()
1812
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001813
1814DataImage = blockimgdiff.DataImage
1815
Doug Zongker96a57e72010-09-26 14:57:41 -07001816# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001817PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001818 "ext4": "EMMC",
1819 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001820 "f2fs": "EMMC",
1821 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001822}
Doug Zongker96a57e72010-09-26 14:57:41 -07001823
1824def GetTypeAndDevice(mount_point, info):
1825 fstab = info["fstab"]
1826 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001827 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1828 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001829 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001830 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001831
1832
1833def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001834 """Parses and converts a PEM-encoded certificate into DER-encoded.
1835
1836 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1837
1838 Returns:
1839 The decoded certificate string.
1840 """
1841 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001842 save = False
1843 for line in data.split("\n"):
1844 if "--END CERTIFICATE--" in line:
1845 break
1846 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001847 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001848 if "--BEGIN CERTIFICATE--" in line:
1849 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001850 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001851 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001852
Tao Bao04e1f012018-02-04 12:13:35 -08001853
1854def ExtractPublicKey(cert):
1855 """Extracts the public key (PEM-encoded) from the given certificate file.
1856
1857 Args:
1858 cert: The certificate filename.
1859
1860 Returns:
1861 The public key string.
1862
1863 Raises:
1864 AssertionError: On non-zero return from 'openssl'.
1865 """
1866 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1867 # While openssl 1.1 writes the key into the given filename followed by '-out',
1868 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1869 # stdout instead.
1870 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1871 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1872 pubkey, stderrdata = proc.communicate()
1873 assert proc.returncode == 0, \
1874 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1875 return pubkey
1876
1877
Doug Zongker412c02f2014-02-13 10:58:24 -08001878def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1879 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001880 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001881
Tao Bao6d5d6232018-03-09 17:04:42 -08001882 Most of the space in the boot and recovery images is just the kernel, which is
1883 identical for the two, so the resulting patch should be efficient. Add it to
1884 the output zip, along with a shell script that is run from init.rc on first
1885 boot to actually do the patching and install the new recovery image.
1886
1887 Args:
1888 input_dir: The top-level input directory of the target-files.zip.
1889 output_sink: The callback function that writes the result.
1890 recovery_img: File object for the recovery image.
1891 boot_img: File objects for the boot image.
1892 info_dict: A dict returned by common.LoadInfoDict() on the input
1893 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001894 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001895 if info_dict is None:
1896 info_dict = OPTIONS.info_dict
1897
Tao Bao6d5d6232018-03-09 17:04:42 -08001898 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001899
Tao Baof2cffbd2015-07-22 12:33:18 -07001900 if full_recovery_image:
1901 output_sink("etc/recovery.img", recovery_img.data)
1902
1903 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08001904 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07001905 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08001906 # With system-root-image, boot and recovery images will have mismatching
1907 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
1908 # to handle such a case.
1909 if system_root_image:
1910 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07001911 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08001912 assert not os.path.exists(path)
1913 else:
1914 diff_program = ["imgdiff"]
1915 if os.path.exists(path):
1916 diff_program.append("-b")
1917 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07001918 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08001919 else:
1920 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07001921
1922 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1923 _, _, patch = d.ComputePatch()
1924 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001925
Dan Albertebb19aa2015-03-27 19:11:53 -07001926 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001927 # The following GetTypeAndDevice()s need to use the path in the target
1928 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001929 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1930 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1931 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001932 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001933
Tao Baof2cffbd2015-07-22 12:33:18 -07001934 if full_recovery_image:
1935 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07001936if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
1937 applypatch \\
1938 --flash /system/etc/recovery.img \\
1939 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
1940 log -t recovery "Installing new recovery image: succeeded" || \\
1941 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07001942else
1943 log -t recovery "Recovery image already installed"
1944fi
1945""" % {'type': recovery_type,
1946 'device': recovery_device,
1947 'sha1': recovery_img.sha1,
1948 'size': recovery_img.size}
1949 else:
1950 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07001951if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1952 applypatch %(bonus_args)s \\
1953 --patch /system/recovery-from-boot.p \\
1954 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
1955 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
1956 log -t recovery "Installing new recovery image: succeeded" || \\
1957 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08001958else
1959 log -t recovery "Recovery image already installed"
1960fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001961""" % {'boot_size': boot_img.size,
1962 'boot_sha1': boot_img.sha1,
1963 'recovery_size': recovery_img.size,
1964 'recovery_sha1': recovery_img.sha1,
1965 'boot_type': boot_type,
1966 'boot_device': boot_device,
1967 'recovery_type': recovery_type,
1968 'recovery_device': recovery_device,
1969 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001970
1971 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001972 # in the L release.
1973 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001974
Tao Bao89fbb0f2017-01-10 10:47:58 -08001975 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001976
1977 output_sink(sh_location, sh)