blob: b5f69a526b53f4467cecb344ecbd05d905126f14 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Doug Zongkerea5d7a92010-09-12 15:26:16 -070017import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070018import errno
Doug Zongkereef39442009-04-02 12:14:19 -070019import getopt
20import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010021import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070022import imp
Doug Zongkereef39442009-04-02 12:14:19 -070023import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080024import platform
Doug Zongkereef39442009-04-02 12:14:19 -070025import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070026import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070027import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080028import string
Doug Zongkereef39442009-04-02 12:14:19 -070029import subprocess
30import sys
31import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070032import threading
33import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070034import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080035from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070036
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070037import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080038import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070039
Dan Albert8b72aef2015-03-23 19:13:21 -070040class Options(object):
41 def __init__(self):
42 platform_search_path = {
43 "linux2": "out/host/linux-x86",
44 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070045 }
Doug Zongker85448772014-09-09 14:59:20 -070046
Dan Albert8b72aef2015-03-23 19:13:21 -070047 self.search_path = platform_search_path.get(sys.platform, None)
48 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080049 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070050 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080052 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070053 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070055 # use otatools built boot_signer by default
56 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070057 self.boot_signer_args = []
58 self.verity_signer_path = None
59 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.verbose = False
61 self.tempfiles = []
62 self.device_specific = None
63 self.extras = {}
64 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070065 self.source_info_dict = None
66 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070068 # Stash size cannot exceed cache_size * threshold.
69 self.cache_size = None
70 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070071
72
73OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070074
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080075
76# Values for "certificate" in apkcerts that mean special things.
77SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
78
Tao Bao9dd909e2017-11-14 11:27:32 -080079
80# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Jaekyun Seokb7735d82017-11-27 17:04:47 +090081AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product', 'dtbo')
Tao Bao9dd909e2017-11-14 11:27:32 -080082
83
Tianjie Xu209db462016-05-24 17:34:52 -070084class ErrorCode(object):
85 """Define error_codes for failures that happen during the actual
86 update package installation.
87
88 Error codes 0-999 are reserved for failures before the package
89 installation (i.e. low battery, package verification failure).
90 Detailed code in 'bootable/recovery/error_code.h' """
91
92 SYSTEM_VERIFICATION_FAILURE = 1000
93 SYSTEM_UPDATE_FAILURE = 1001
94 SYSTEM_UNEXPECTED_CONTENTS = 1002
95 SYSTEM_NONZERO_CONTENTS = 1003
96 SYSTEM_RECOVER_FAILURE = 1004
97 VENDOR_VERIFICATION_FAILURE = 2000
98 VENDOR_UPDATE_FAILURE = 2001
99 VENDOR_UNEXPECTED_CONTENTS = 2002
100 VENDOR_NONZERO_CONTENTS = 2003
101 VENDOR_RECOVER_FAILURE = 2004
102 OEM_PROP_MISMATCH = 3000
103 FINGERPRINT_MISMATCH = 3001
104 THUMBPRINT_MISMATCH = 3002
105 OLDER_BUILD = 3003
106 DEVICE_MISMATCH = 3004
107 BAD_PATCH_FILE = 3005
108 INSUFFICIENT_CACHE_SPACE = 3006
109 TUNE_PARTITION_FAILURE = 3007
110 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800111
Tao Bao80921982018-03-21 21:02:19 -0700112
Dan Albert8b72aef2015-03-23 19:13:21 -0700113class ExternalError(RuntimeError):
114 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700115
116
Tao Bao39451582017-05-04 11:10:47 -0700117def Run(args, verbose=None, **kwargs):
118 """Create and return a subprocess.Popen object.
119
120 Caller can specify if the command line should be printed. The global
121 OPTIONS.verbose will be used if not specified.
122 """
123 if verbose is None:
124 verbose = OPTIONS.verbose
125 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800126 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700127 return subprocess.Popen(args, **kwargs)
128
129
Tao Baoc765cca2018-01-31 17:32:40 -0800130def RoundUpTo4K(value):
131 rounded_up = value + 4095
132 return rounded_up - (rounded_up % 4096)
133
134
Ying Wang7e6d4e42010-12-13 16:25:36 -0800135def CloseInheritedPipes():
136 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
137 before doing other work."""
138 if platform.system() != "Darwin":
139 return
140 for d in range(3, 1025):
141 try:
142 stat = os.fstat(d)
143 if stat is not None:
144 pipebit = stat[0] & 0x1000
145 if pipebit != 0:
146 os.close(d)
147 except OSError:
148 pass
149
150
Tao Bao2c15d9e2015-07-09 11:51:16 -0700151def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700152 """Read and parse the META/misc_info.txt key/value pairs from the
153 input target files and return a dict."""
154
Doug Zongkerc9253822014-02-04 12:17:58 -0800155 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700156 if isinstance(input_file, zipfile.ZipFile):
157 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800158 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700159 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800160 try:
161 with open(path) as f:
162 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700163 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800164 if e.errno == errno.ENOENT:
165 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800166
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700167 try:
Michael Runge6e836112014-04-15 17:40:21 -0700168 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700169 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800170 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700171
Tao Bao6cd54732017-02-27 15:12:05 -0800172 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800173 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800174
Tao Bao84e75682015-07-19 02:38:53 -0700175 # A few properties are stored as links to the files in the out/ directory.
176 # It works fine with the build system. However, they are no longer available
177 # when (re)generating from target_files zip. If input_dir is not None, we
178 # are doing repacking. Redirect those properties to the actual files in the
179 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700180 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400181 # We carry a copy of file_contexts.bin under META/. If not available,
182 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700183 # to build images than the one running on device, such as when enabling
184 # system_root_image. In that case, we must have the one for image
185 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700186 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
187 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700188 if d.get("system_root_image") == "true":
189 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700190 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700191 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700192 if not os.path.exists(fc_config):
193 fc_config = None
194
195 if fc_config:
196 d["selinux_fc"] = fc_config
197
Tao Bao84e75682015-07-19 02:38:53 -0700198 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
199 if d.get("system_root_image") == "true":
200 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
201 d["ramdisk_fs_config"] = os.path.join(
202 input_dir, "META", "root_filesystem_config.txt")
203
Tao Baof54216f2016-03-29 15:12:37 -0700204 # Redirect {system,vendor}_base_fs_file.
205 if "system_base_fs_file" in d:
206 basename = os.path.basename(d["system_base_fs_file"])
207 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700208 if os.path.exists(system_base_fs_file):
209 d["system_base_fs_file"] = system_base_fs_file
210 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800211 print("Warning: failed to find system base fs file: %s" % (
212 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700213 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700214
215 if "vendor_base_fs_file" in d:
216 basename = os.path.basename(d["vendor_base_fs_file"])
217 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700218 if os.path.exists(vendor_base_fs_file):
219 d["vendor_base_fs_file"] = vendor_base_fs_file
220 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800221 print("Warning: failed to find vendor base fs file: %s" % (
222 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700223 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700224
Doug Zongker37974732010-09-16 17:44:38 -0700225 def makeint(key):
226 if key in d:
227 d[key] = int(d[key], 0)
228
229 makeint("recovery_api_version")
230 makeint("blocksize")
231 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700232 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700233 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700234 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700235 makeint("recovery_size")
236 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800237 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700238
Tianjie Xucfa86222016-03-07 16:31:19 -0800239 system_root_image = d.get("system_root_image", None) == "true"
240 if d.get("no_recovery", None) != "true":
241 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800242 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800243 recovery_fstab_path, system_root_image)
244 elif d.get("recovery_as_boot", None) == "true":
245 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
246 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
247 recovery_fstab_path, system_root_image)
248 else:
249 d["fstab"] = None
250
Tao Baobcd1d162017-08-26 13:10:26 -0700251 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
252 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Tao Bao12d87fc2018-01-31 12:18:52 -0800253
254 # Set up the salt (based on fingerprint or thumbprint) that will be used when
255 # adding AVB footer.
256 if d.get("avb_enable") == "true":
257 fp = None
258 if "build.prop" in d:
259 build_prop = d["build.prop"]
260 if "ro.build.fingerprint" in build_prop:
261 fp = build_prop["ro.build.fingerprint"]
262 elif "ro.build.thumbprint" in build_prop:
263 fp = build_prop["ro.build.thumbprint"]
264 if fp:
265 d["avb_salt"] = sha256(fp).hexdigest()
266
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700267 return d
268
Tao Baod1de6f32017-03-01 16:38:48 -0800269
Tao Baobcd1d162017-08-26 13:10:26 -0700270def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700271 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700272 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700273 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700274 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700275 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700276 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700277
Tao Baod1de6f32017-03-01 16:38:48 -0800278
Michael Runge6e836112014-04-15 17:40:21 -0700279def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700280 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700281 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700282 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700283 if not line or line.startswith("#"):
284 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700285 if "=" in line:
286 name, value = line.split("=", 1)
287 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700288 return d
289
Tao Baod1de6f32017-03-01 16:38:48 -0800290
Tianjie Xucfa86222016-03-07 16:31:19 -0800291def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
292 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700293 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800294 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700295 self.mount_point = mount_point
296 self.fs_type = fs_type
297 self.device = device
298 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700299 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700300
301 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800302 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700303 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800304 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700305 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700306
Tao Baod1de6f32017-03-01 16:38:48 -0800307 assert fstab_version == 2
308
309 d = {}
310 for line in data.split("\n"):
311 line = line.strip()
312 if not line or line.startswith("#"):
313 continue
314
315 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
316 pieces = line.split()
317 if len(pieces) != 5:
318 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
319
320 # Ignore entries that are managed by vold.
321 options = pieces[4]
322 if "voldmanaged=" in options:
323 continue
324
325 # It's a good line, parse it.
326 length = 0
327 options = options.split(",")
328 for i in options:
329 if i.startswith("length="):
330 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800331 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800332 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700333 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800334
Tao Baod1de6f32017-03-01 16:38:48 -0800335 mount_flags = pieces[3]
336 # Honor the SELinux context if present.
337 context = None
338 for i in mount_flags.split(","):
339 if i.startswith("context="):
340 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800341
Tao Baod1de6f32017-03-01 16:38:48 -0800342 mount_point = pieces[1]
343 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
344 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800345
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700346 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700347 # system. Other areas assume system is always at "/system" so point /system
348 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700349 if system_root_image:
350 assert not d.has_key("/system") and d.has_key("/")
351 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700352 return d
353
354
Doug Zongker37974732010-09-16 17:44:38 -0700355def DumpInfoDict(d):
356 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800357 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700358
Dan Albert8b72aef2015-03-23 19:13:21 -0700359
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800360def AppendAVBSigningArgs(cmd, partition):
361 """Append signing arguments for avbtool."""
362 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
363 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
364 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
365 if key_path and algorithm:
366 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700367 avb_salt = OPTIONS.info_dict.get("avb_salt")
368 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
369 if avb_salt and partition != "vbmeta":
370 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800371
372
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700373def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800374 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700375 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700376
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700377 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800378 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
379 we are building a two-step special image (i.e. building a recovery image to
380 be loaded into /boot in two-step OTAs).
381
382 Return the image data, or None if sourcedir does not appear to contains files
383 for building the requested image.
384 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700385
386 def make_ramdisk():
387 ramdisk_img = tempfile.NamedTemporaryFile()
388
389 if os.access(fs_config_file, os.F_OK):
390 cmd = ["mkbootfs", "-f", fs_config_file,
391 os.path.join(sourcedir, "RAMDISK")]
392 else:
393 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
394 p1 = Run(cmd, stdout=subprocess.PIPE)
395 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
396
397 p2.wait()
398 p1.wait()
399 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
400 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
401
402 return ramdisk_img
403
404 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
405 return None
406
407 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700408 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700409
Doug Zongkerd5131602012-08-02 14:46:42 -0700410 if info_dict is None:
411 info_dict = OPTIONS.info_dict
412
Doug Zongkereef39442009-04-02 12:14:19 -0700413 img = tempfile.NamedTemporaryFile()
414
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700415 if has_ramdisk:
416 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700417
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800418 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
419 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
420
421 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700422
Benoit Fradina45a8682014-07-14 21:00:43 +0200423 fn = os.path.join(sourcedir, "second")
424 if os.access(fn, os.F_OK):
425 cmd.append("--second")
426 cmd.append(fn)
427
Doug Zongker171f1cd2009-06-15 22:36:37 -0700428 fn = os.path.join(sourcedir, "cmdline")
429 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700430 cmd.append("--cmdline")
431 cmd.append(open(fn).read().rstrip("\n"))
432
433 fn = os.path.join(sourcedir, "base")
434 if os.access(fn, os.F_OK):
435 cmd.append("--base")
436 cmd.append(open(fn).read().rstrip("\n"))
437
Ying Wang4de6b5b2010-08-25 14:29:34 -0700438 fn = os.path.join(sourcedir, "pagesize")
439 if os.access(fn, os.F_OK):
440 cmd.append("--pagesize")
441 cmd.append(open(fn).read().rstrip("\n"))
442
Doug Zongkerd5131602012-08-02 14:46:42 -0700443 args = info_dict.get("mkbootimg_args", None)
444 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700445 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700446
Sami Tolvanen3303d902016-03-15 16:49:30 +0000447 args = info_dict.get("mkbootimg_version_args", None)
448 if args and args.strip():
449 cmd.extend(shlex.split(args))
450
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700451 if has_ramdisk:
452 cmd.extend(["--ramdisk", ramdisk_img.name])
453
Tao Baod95e9fd2015-03-29 23:07:41 -0700454 img_unsigned = None
455 if info_dict.get("vboot", None):
456 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700457 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700458 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700459 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700460
Tao Baobf70c312017-07-11 17:27:55 -0700461 # "boot" or "recovery", without extension.
462 partition_name = os.path.basename(sourcedir).lower()
463
Doug Zongker38a649f2009-06-17 09:07:09 -0700464 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700465 p.communicate()
Tao Baobf70c312017-07-11 17:27:55 -0700466 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700467
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100468 if (info_dict.get("boot_signer", None) == "true" and
469 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800470 # Hard-code the path as "/boot" for two-step special recovery image (which
471 # will be loaded into /boot during the two-step OTA).
472 if two_step_image:
473 path = "/boot"
474 else:
Tao Baobf70c312017-07-11 17:27:55 -0700475 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700476 cmd = [OPTIONS.boot_signer_path]
477 cmd.extend(OPTIONS.boot_signer_args)
478 cmd.extend([path, img.name,
479 info_dict["verity_key"] + ".pk8",
480 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700481 p = Run(cmd, stdout=subprocess.PIPE)
482 p.communicate()
483 assert p.returncode == 0, "boot_signer of %s image failed" % path
484
Tao Baod95e9fd2015-03-29 23:07:41 -0700485 # Sign the image if vboot is non-empty.
486 elif info_dict.get("vboot", None):
Tao Baobf70c312017-07-11 17:27:55 -0700487 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700488 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800489 # We have switched from the prebuilt futility binary to using the tool
490 # (futility-host) built from the source. Override the setting in the old
491 # TF.zip.
492 futility = info_dict["futility"]
493 if futility.startswith("prebuilts/"):
494 futility = "futility-host"
495 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700496 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700497 info_dict["vboot_key"] + ".vbprivk",
498 info_dict["vboot_subkey"] + ".vbprivk",
499 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700500 img.name]
501 p = Run(cmd, stdout=subprocess.PIPE)
502 p.communicate()
503 assert p.returncode == 0, "vboot_signer of %s image failed" % path
504
Tao Baof3282b42015-04-01 11:21:55 -0700505 # Clean up the temp files.
506 img_unsigned.close()
507 img_keyblock.close()
508
David Zeuthen8fecb282017-12-01 16:24:01 -0500509 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800510 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700511 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500512 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400513 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700514 "--partition_size", str(part_size), "--partition_name",
515 partition_name]
516 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500517 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400518 if args and args.strip():
519 cmd.extend(shlex.split(args))
520 p = Run(cmd, stdout=subprocess.PIPE)
521 p.communicate()
522 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c312017-07-11 17:27:55 -0700523 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500524
525 img.seek(os.SEEK_SET, 0)
526 data = img.read()
527
528 if has_ramdisk:
529 ramdisk_img.close()
530 img.close()
531
532 return data
533
534
Doug Zongkerd5131602012-08-02 14:46:42 -0700535def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800536 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700537 """Return a File object with the desired bootable image.
538
539 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
540 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
541 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700542
Doug Zongker55d93282011-01-25 17:03:34 -0800543 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
544 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800545 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800546 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700547
548 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
549 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800550 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700551 return File.FromLocalFile(name, prebuilt_path)
552
Tao Bao89fbb0f2017-01-10 10:47:58 -0800553 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700554
555 if info_dict is None:
556 info_dict = OPTIONS.info_dict
557
558 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800559 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
560 # for recovery.
561 has_ramdisk = (info_dict.get("system_root_image") != "true" or
562 prebuilt_name != "boot.img" or
563 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700564
Doug Zongker6f1d0312014-08-22 08:07:12 -0700565 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400566 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
567 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800568 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700569 if data:
570 return File(name, data)
571 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800572
Doug Zongkereef39442009-04-02 12:14:19 -0700573
Narayan Kamatha07bf042017-08-14 14:49:21 +0100574def Gunzip(in_filename, out_filename):
575 """Gunzip the given gzip compressed file to a given output file.
576 """
577 with gzip.open(in_filename, "rb") as in_file, open(out_filename, "wb") as out_file:
578 shutil.copyfileobj(in_file, out_file)
579
580
Doug Zongker75f17362009-12-08 13:46:44 -0800581def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800582 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800583
Tao Bao1c830bf2017-12-25 10:43:47 -0800584 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
585 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800586
Tao Bao1c830bf2017-12-25 10:43:47 -0800587 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800588 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800589 """
Doug Zongkereef39442009-04-02 12:14:19 -0700590
Doug Zongker55d93282011-01-25 17:03:34 -0800591 def unzip_to_dir(filename, dirname):
592 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
593 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800594 cmd.extend(pattern)
Tao Bao80921982018-03-21 21:02:19 -0700595 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
596 stdoutdata, _ = p.communicate()
Doug Zongker55d93282011-01-25 17:03:34 -0800597 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700598 raise ExternalError(
599 "Failed to unzip input target-files \"{}\":\n{}".format(
600 filename, stdoutdata))
Doug Zongker55d93282011-01-25 17:03:34 -0800601
Tao Bao1c830bf2017-12-25 10:43:47 -0800602 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800603 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
604 if m:
605 unzip_to_dir(m.group(1), tmp)
606 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
607 filename = m.group(1)
608 else:
609 unzip_to_dir(filename, tmp)
610
Tao Baodba59ee2018-01-09 13:21:02 -0800611 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700612
613
Tao Baoe709b092018-02-07 12:40:00 -0800614def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -0800615 """Returns a SparseImage object suitable for passing to BlockImageDiff.
616
617 This function loads the specified sparse image from the given path, and
618 performs additional processing for OTA purpose. For example, it always adds
619 block 0 to clobbered blocks list. It also detects files that cannot be
620 reconstructed from the block list, for whom we should avoid applying imgdiff.
621
622 Args:
623 which: The partition name, which must be "system" or "vendor".
624 tmpdir: The directory that contains the prebuilt image and block map file.
625 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800626 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -0800627
628 Returns:
629 A SparseImage object, with file_map info loaded.
630 """
631 assert which in ("system", "vendor")
632
633 path = os.path.join(tmpdir, "IMAGES", which + ".img")
634 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
635
636 # The image and map files must have been created prior to calling
637 # ota_from_target_files.py (since LMP).
638 assert os.path.exists(path) and os.path.exists(mappath)
639
640 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
641 # it to clobbered_blocks so that it will be written to the target
642 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
643 clobbered_blocks = "0"
644
Tao Baoe709b092018-02-07 12:40:00 -0800645 image = sparse_img.SparseImage(path, mappath, clobbered_blocks,
646 allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -0800647
648 # block.map may contain less blocks, because mke2fs may skip allocating blocks
649 # if they contain all zeros. We can't reconstruct such a file from its block
650 # list. Tag such entries accordingly. (Bug: 65213616)
651 for entry in image.file_map:
652 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar".
653 arcname = string.replace(entry, which, which.upper(), 1)[1:]
654 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
655 if arcname not in input_zip.namelist():
656 continue
657
658 info = input_zip.getinfo(arcname)
659 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800660
661 # If a RangeSet has been tagged as using shared blocks while loading the
662 # image, its block list must be already incomplete due to that reason. Don't
663 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
664 if ranges.extra.get('uses_shared_blocks'):
665 continue
666
Tao Baoc765cca2018-01-31 17:32:40 -0800667 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
668 ranges.extra['incomplete'] = True
669
670 return image
671
672
Doug Zongkereef39442009-04-02 12:14:19 -0700673def GetKeyPasswords(keylist):
674 """Given a list of keys, prompt the user to enter passwords for
675 those which require them. Return a {key: password} dict. password
676 will be None if the key has no password."""
677
Doug Zongker8ce7c252009-05-22 13:34:54 -0700678 no_passwords = []
679 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700680 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700681 devnull = open("/dev/null", "w+b")
682 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800683 # We don't need a password for things that aren't really keys.
684 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700685 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700686 continue
687
T.R. Fullhart37e10522013-03-18 10:31:26 -0700688 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700689 "-inform", "DER", "-nocrypt"],
690 stdin=devnull.fileno(),
691 stdout=devnull.fileno(),
692 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700693 p.communicate()
694 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700695 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700696 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700697 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700698 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
699 "-inform", "DER", "-passin", "pass:"],
700 stdin=devnull.fileno(),
701 stdout=devnull.fileno(),
702 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700703 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700704 if p.returncode == 0:
705 # Encrypted key with empty string as password.
706 key_passwords[k] = ''
707 elif stderr.startswith('Error decrypting key'):
708 # Definitely encrypted key.
709 # It would have said "Error reading key" if it didn't parse correctly.
710 need_passwords.append(k)
711 else:
712 # Potentially, a type of key that openssl doesn't understand.
713 # We'll let the routines in signapk.jar handle it.
714 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700715 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700716
T.R. Fullhart37e10522013-03-18 10:31:26 -0700717 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700718 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700719 return key_passwords
720
721
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800722def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700723 """Gets the minSdkVersion declared in the APK.
724
725 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
726 This can be both a decimal number (API Level) or a codename.
727
728 Args:
729 apk_name: The APK filename.
730
731 Returns:
732 The parsed SDK version string.
733
734 Raises:
735 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800736 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700737 proc = Run(
738 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
739 stderr=subprocess.PIPE)
740 stdoutdata, stderrdata = proc.communicate()
741 if proc.returncode != 0:
742 raise ExternalError(
743 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
744 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800745
Tao Baof47bf0f2018-03-21 23:28:51 -0700746 for line in stdoutdata.split("\n"):
747 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800748 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
749 if m:
750 return m.group(1)
751 raise ExternalError("No minSdkVersion returned by aapt")
752
753
754def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700755 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800756
Tao Baof47bf0f2018-03-21 23:28:51 -0700757 If minSdkVersion is set to a codename, it is translated to a number using the
758 provided map.
759
760 Args:
761 apk_name: The APK filename.
762
763 Returns:
764 The parsed SDK version number.
765
766 Raises:
767 ExternalError: On failing to get the min SDK version number.
768 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800769 version = GetMinSdkVersion(apk_name)
770 try:
771 return int(version)
772 except ValueError:
773 # Not a decimal number. Codename?
774 if version in codename_to_api_level_map:
775 return codename_to_api_level_map[version]
776 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700777 raise ExternalError(
778 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
779 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800780
781
782def SignFile(input_name, output_name, key, password, min_api_level=None,
783 codename_to_api_level_map=dict(),
784 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700785 """Sign the input_name zip/jar/apk, producing output_name. Use the
786 given key and password (the latter may be None if the key does not
787 have a password.
788
Doug Zongker951495f2009-08-14 12:44:19 -0700789 If whole_file is true, use the "-w" option to SignApk to embed a
790 signature that covers the whole file in the archive comment of the
791 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800792
793 min_api_level is the API Level (int) of the oldest platform this file may end
794 up on. If not specified for an APK, the API Level is obtained by interpreting
795 the minSdkVersion attribute of the APK's AndroidManifest.xml.
796
797 codename_to_api_level_map is needed to translate the codename which may be
798 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700799 """
Doug Zongker951495f2009-08-14 12:44:19 -0700800
Alex Klyubin9667b182015-12-10 13:38:50 -0800801 java_library_path = os.path.join(
802 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
803
Tao Baoe95540e2016-11-08 12:08:53 -0800804 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
805 ["-Djava.library.path=" + java_library_path,
806 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
807 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700808 if whole_file:
809 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800810
811 min_sdk_version = min_api_level
812 if min_sdk_version is None:
813 if not whole_file:
814 min_sdk_version = GetMinSdkVersionInt(
815 input_name, codename_to_api_level_map)
816 if min_sdk_version is not None:
817 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
818
T.R. Fullhart37e10522013-03-18 10:31:26 -0700819 cmd.extend([key + OPTIONS.public_key_suffix,
820 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800821 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700822
Tao Bao80921982018-03-21 21:02:19 -0700823 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
824 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700825 if password is not None:
826 password += "\n"
Tao Bao80921982018-03-21 21:02:19 -0700827 stdoutdata, _ = p.communicate(password)
Doug Zongkereef39442009-04-02 12:14:19 -0700828 if p.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700829 raise ExternalError(
830 "Failed to run signapk.jar: return code {}:\n{}".format(
831 p.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700832
Doug Zongkereef39442009-04-02 12:14:19 -0700833
Doug Zongker37974732010-09-16 17:44:38 -0700834def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800835 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700836
Tao Bao9dd909e2017-11-14 11:27:32 -0800837 For non-AVB images, raise exception if the data is too big. Print a warning
838 if the data is nearing the maximum size.
839
840 For AVB images, the actual image size should be identical to the limit.
841
842 Args:
843 data: A string that contains all the data for the partition.
844 target: The partition name. The ".img" suffix is optional.
845 info_dict: The dict to be looked up for relevant info.
846 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700847 if target.endswith(".img"):
848 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700849 mount_point = "/" + target
850
Ying Wangf8824af2014-06-03 14:07:27 -0700851 fs_type = None
852 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700853 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700854 if mount_point == "/userdata":
855 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700856 p = info_dict["fstab"][mount_point]
857 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800858 device = p.device
859 if "/" in device:
860 device = device[device.rfind("/")+1:]
861 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700862 if not fs_type or not limit:
863 return
Doug Zongkereef39442009-04-02 12:14:19 -0700864
Andrew Boie0f9aec82012-02-14 09:32:52 -0800865 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800866 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
867 # path.
868 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
869 if size != limit:
870 raise ExternalError(
871 "Mismatching image size for %s: expected %d actual %d" % (
872 target, limit, size))
873 else:
874 pct = float(size) * 100.0 / limit
875 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
876 if pct >= 99.0:
877 raise ExternalError(msg)
878 elif pct >= 95.0:
879 print("\n WARNING: %s\n" % (msg,))
880 elif OPTIONS.verbose:
881 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700882
883
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800884def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800885 """Parses the APK certs info from a given target-files zip.
886
887 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
888 tuple with the following elements: (1) a dictionary that maps packages to
889 certs (based on the "certificate" and "private_key" attributes in the file;
890 (2) a string representing the extension of compressed APKs in the target files
891 (e.g ".gz", ".bro").
892
893 Args:
894 tf_zip: The input target_files ZipFile (already open).
895
896 Returns:
897 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
898 the extension string of compressed APKs (e.g. ".gz"), or None if there's
899 no compressed APKs.
900 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800901 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100902 compressed_extension = None
903
Tao Bao0f990332017-09-08 19:02:54 -0700904 # META/apkcerts.txt contains the info for _all_ the packages known at build
905 # time. Filter out the ones that are not installed.
906 installed_files = set()
907 for name in tf_zip.namelist():
908 basename = os.path.basename(name)
909 if basename:
910 installed_files.add(basename)
911
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800912 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
913 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700914 if not line:
915 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800916 m = re.match(
917 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
918 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
919 line)
920 if not m:
921 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100922
Tao Bao818ddf52018-01-05 11:17:34 -0800923 matches = m.groupdict()
924 cert = matches["CERT"]
925 privkey = matches["PRIVKEY"]
926 name = matches["NAME"]
927 this_compressed_extension = matches["COMPRESSED"]
928
929 public_key_suffix_len = len(OPTIONS.public_key_suffix)
930 private_key_suffix_len = len(OPTIONS.private_key_suffix)
931 if cert in SPECIAL_CERT_STRINGS and not privkey:
932 certmap[name] = cert
933 elif (cert.endswith(OPTIONS.public_key_suffix) and
934 privkey.endswith(OPTIONS.private_key_suffix) and
935 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
936 certmap[name] = cert[:-public_key_suffix_len]
937 else:
938 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
939
940 if not this_compressed_extension:
941 continue
942
943 # Only count the installed files.
944 filename = name + '.' + this_compressed_extension
945 if filename not in installed_files:
946 continue
947
948 # Make sure that all the values in the compression map have the same
949 # extension. We don't support multiple compression methods in the same
950 # system image.
951 if compressed_extension:
952 if this_compressed_extension != compressed_extension:
953 raise ValueError(
954 "Multiple compressed extensions: {} vs {}".format(
955 compressed_extension, this_compressed_extension))
956 else:
957 compressed_extension = this_compressed_extension
958
959 return (certmap,
960 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800961
962
Doug Zongkereef39442009-04-02 12:14:19 -0700963COMMON_DOCSTRING = """
964 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700965 Prepend <dir>/bin to the list of places to search for binaries
966 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700967
Doug Zongker05d3dea2009-06-22 11:32:31 -0700968 -s (--device_specific) <file>
969 Path to the python module containing device-specific
970 releasetools code.
971
Doug Zongker8bec09e2009-11-30 15:37:14 -0800972 -x (--extra) <key=value>
973 Add a key/value pair to the 'extras' dict, which device-specific
974 extension code may look at.
975
Doug Zongkereef39442009-04-02 12:14:19 -0700976 -v (--verbose)
977 Show command lines being executed.
978
979 -h (--help)
980 Display this usage message and exit.
981"""
982
983def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800984 print(docstring.rstrip("\n"))
985 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700986
987
988def ParseOptions(argv,
989 docstring,
990 extra_opts="", extra_long_opts=(),
991 extra_option_handler=None):
992 """Parse the options in argv and return any arguments that aren't
993 flags. docstring is the calling module's docstring, to be displayed
994 for errors and -h. extra_opts and extra_long_opts are for flags
995 defined by the caller, which are processed by passing them to
996 extra_option_handler."""
997
998 try:
999 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001000 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001001 ["help", "verbose", "path=", "signapk_path=",
1002 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001003 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001004 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1005 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001006 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001007 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001008 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001009 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001010 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001011 sys.exit(2)
1012
Doug Zongkereef39442009-04-02 12:14:19 -07001013 for o, a in opts:
1014 if o in ("-h", "--help"):
1015 Usage(docstring)
1016 sys.exit()
1017 elif o in ("-v", "--verbose"):
1018 OPTIONS.verbose = True
1019 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001020 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001021 elif o in ("--signapk_path",):
1022 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001023 elif o in ("--signapk_shared_library_path",):
1024 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001025 elif o in ("--extra_signapk_args",):
1026 OPTIONS.extra_signapk_args = shlex.split(a)
1027 elif o in ("--java_path",):
1028 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001029 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001030 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001031 elif o in ("--public_key_suffix",):
1032 OPTIONS.public_key_suffix = a
1033 elif o in ("--private_key_suffix",):
1034 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001035 elif o in ("--boot_signer_path",):
1036 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001037 elif o in ("--boot_signer_args",):
1038 OPTIONS.boot_signer_args = shlex.split(a)
1039 elif o in ("--verity_signer_path",):
1040 OPTIONS.verity_signer_path = a
1041 elif o in ("--verity_signer_args",):
1042 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001043 elif o in ("-s", "--device_specific"):
1044 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001045 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001046 key, value = a.split("=", 1)
1047 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001048 else:
1049 if extra_option_handler is None or not extra_option_handler(o, a):
1050 assert False, "unknown option \"%s\"" % (o,)
1051
Doug Zongker85448772014-09-09 14:59:20 -07001052 if OPTIONS.search_path:
1053 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1054 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001055
1056 return args
1057
1058
Tao Bao4c851b12016-09-19 13:54:38 -07001059def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001060 """Make a temp file and add it to the list of things to be deleted
1061 when Cleanup() is called. Return the filename."""
1062 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1063 os.close(fd)
1064 OPTIONS.tempfiles.append(fn)
1065 return fn
1066
1067
Tao Bao1c830bf2017-12-25 10:43:47 -08001068def MakeTempDir(prefix='tmp', suffix=''):
1069 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1070
1071 Returns:
1072 The absolute pathname of the new directory.
1073 """
1074 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1075 OPTIONS.tempfiles.append(dir_name)
1076 return dir_name
1077
1078
Doug Zongkereef39442009-04-02 12:14:19 -07001079def Cleanup():
1080 for i in OPTIONS.tempfiles:
1081 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001082 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001083 else:
1084 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001085 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001086
1087
1088class PasswordManager(object):
1089 def __init__(self):
1090 self.editor = os.getenv("EDITOR", None)
1091 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
1092
1093 def GetPasswords(self, items):
1094 """Get passwords corresponding to each string in 'items',
1095 returning a dict. (The dict may have keys in addition to the
1096 values in 'items'.)
1097
1098 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1099 user edit that file to add more needed passwords. If no editor is
1100 available, or $ANDROID_PW_FILE isn't define, prompts the user
1101 interactively in the ordinary way.
1102 """
1103
1104 current = self.ReadFile()
1105
1106 first = True
1107 while True:
1108 missing = []
1109 for i in items:
1110 if i not in current or not current[i]:
1111 missing.append(i)
1112 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001113 if not missing:
1114 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001115
1116 for i in missing:
1117 current[i] = ""
1118
1119 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001120 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001121 answer = raw_input("try to edit again? [y]> ").strip()
1122 if answer and answer[0] not in 'yY':
1123 raise RuntimeError("key passwords unavailable")
1124 first = False
1125
1126 current = self.UpdateAndReadFile(current)
1127
Dan Albert8b72aef2015-03-23 19:13:21 -07001128 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001129 """Prompt the user to enter a value (password) for each key in
1130 'current' whose value is fales. Returns a new dict with all the
1131 values.
1132 """
1133 result = {}
1134 for k, v in sorted(current.iteritems()):
1135 if v:
1136 result[k] = v
1137 else:
1138 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001139 result[k] = getpass.getpass(
1140 "Enter password for %s key> " % k).strip()
1141 if result[k]:
1142 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001143 return result
1144
1145 def UpdateAndReadFile(self, current):
1146 if not self.editor or not self.pwfile:
1147 return self.PromptResult(current)
1148
1149 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001150 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001151 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1152 f.write("# (Additional spaces are harmless.)\n\n")
1153
1154 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001155 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1156 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001157 f.write("[[[ %s ]]] %s\n" % (v, k))
1158 if not v and first_line is None:
1159 # position cursor on first line with no password.
1160 first_line = i + 4
1161 f.close()
1162
1163 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1164 _, _ = p.communicate()
1165
1166 return self.ReadFile()
1167
1168 def ReadFile(self):
1169 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001170 if self.pwfile is None:
1171 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001172 try:
1173 f = open(self.pwfile, "r")
1174 for line in f:
1175 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001176 if not line or line[0] == '#':
1177 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001178 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1179 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001180 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001181 else:
1182 result[m.group(2)] = m.group(1)
1183 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001184 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001185 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001186 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001187 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001188
1189
Dan Albert8e0178d2015-01-27 15:53:15 -08001190def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1191 compress_type=None):
1192 import datetime
1193
1194 # http://b/18015246
1195 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1196 # for files larger than 2GiB. We can work around this by adjusting their
1197 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1198 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1199 # it isn't clear to me exactly what circumstances cause this).
1200 # `zipfile.write()` must be used directly to work around this.
1201 #
1202 # This mess can be avoided if we port to python3.
1203 saved_zip64_limit = zipfile.ZIP64_LIMIT
1204 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1205
1206 if compress_type is None:
1207 compress_type = zip_file.compression
1208 if arcname is None:
1209 arcname = filename
1210
1211 saved_stat = os.stat(filename)
1212
1213 try:
1214 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1215 # file to be zipped and reset it when we're done.
1216 os.chmod(filename, perms)
1217
1218 # Use a fixed timestamp so the output is repeatable.
1219 epoch = datetime.datetime.fromtimestamp(0)
1220 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1221 os.utime(filename, (timestamp, timestamp))
1222
1223 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1224 finally:
1225 os.chmod(filename, saved_stat.st_mode)
1226 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1227 zipfile.ZIP64_LIMIT = saved_zip64_limit
1228
1229
Tao Bao58c1b962015-05-20 09:32:18 -07001230def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001231 compress_type=None):
1232 """Wrap zipfile.writestr() function to work around the zip64 limit.
1233
1234 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1235 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1236 when calling crc32(bytes).
1237
1238 But it still works fine to write a shorter string into a large zip file.
1239 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1240 when we know the string won't be too long.
1241 """
1242
1243 saved_zip64_limit = zipfile.ZIP64_LIMIT
1244 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1245
1246 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1247 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001248 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001249 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001250 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001251 else:
Tao Baof3282b42015-04-01 11:21:55 -07001252 zinfo = zinfo_or_arcname
1253
1254 # If compress_type is given, it overrides the value in zinfo.
1255 if compress_type is not None:
1256 zinfo.compress_type = compress_type
1257
Tao Bao58c1b962015-05-20 09:32:18 -07001258 # If perms is given, it has a priority.
1259 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001260 # If perms doesn't set the file type, mark it as a regular file.
1261 if perms & 0o770000 == 0:
1262 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001263 zinfo.external_attr = perms << 16
1264
Tao Baof3282b42015-04-01 11:21:55 -07001265 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001266 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1267
Dan Albert8b72aef2015-03-23 19:13:21 -07001268 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001269 zipfile.ZIP64_LIMIT = saved_zip64_limit
1270
1271
Tao Bao89d7ab22017-12-14 17:05:33 -08001272def ZipDelete(zip_filename, entries):
1273 """Deletes entries from a ZIP file.
1274
1275 Since deleting entries from a ZIP file is not supported, it shells out to
1276 'zip -d'.
1277
1278 Args:
1279 zip_filename: The name of the ZIP file.
1280 entries: The name of the entry, or the list of names to be deleted.
1281
1282 Raises:
1283 AssertionError: In case of non-zero return from 'zip'.
1284 """
1285 if isinstance(entries, basestring):
1286 entries = [entries]
1287 cmd = ["zip", "-d", zip_filename] + entries
1288 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1289 stdoutdata, _ = proc.communicate()
1290 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1291 stdoutdata)
1292
1293
Tao Baof3282b42015-04-01 11:21:55 -07001294def ZipClose(zip_file):
1295 # http://b/18015246
1296 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1297 # central directory.
1298 saved_zip64_limit = zipfile.ZIP64_LIMIT
1299 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1300
1301 zip_file.close()
1302
1303 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001304
1305
1306class DeviceSpecificParams(object):
1307 module = None
1308 def __init__(self, **kwargs):
1309 """Keyword arguments to the constructor become attributes of this
1310 object, which is passed to all functions in the device-specific
1311 module."""
1312 for k, v in kwargs.iteritems():
1313 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001314 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001315
1316 if self.module is None:
1317 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001318 if not path:
1319 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001320 try:
1321 if os.path.isdir(path):
1322 info = imp.find_module("releasetools", [path])
1323 else:
1324 d, f = os.path.split(path)
1325 b, x = os.path.splitext(f)
1326 if x == ".py":
1327 f = b
1328 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001329 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001330 self.module = imp.load_module("device_specific", *info)
1331 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001332 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001333
1334 def _DoCall(self, function_name, *args, **kwargs):
1335 """Call the named function in the device-specific module, passing
1336 the given args and kwargs. The first argument to the call will be
1337 the DeviceSpecific object itself. If there is no module, or the
1338 module does not define the function, return the value of the
1339 'default' kwarg (which itself defaults to None)."""
1340 if self.module is None or not hasattr(self.module, function_name):
1341 return kwargs.get("default", None)
1342 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1343
1344 def FullOTA_Assertions(self):
1345 """Called after emitting the block of assertions at the top of a
1346 full OTA package. Implementations can add whatever additional
1347 assertions they like."""
1348 return self._DoCall("FullOTA_Assertions")
1349
Doug Zongkere5ff5902012-01-17 10:55:37 -08001350 def FullOTA_InstallBegin(self):
1351 """Called at the start of full OTA installation."""
1352 return self._DoCall("FullOTA_InstallBegin")
1353
Doug Zongker05d3dea2009-06-22 11:32:31 -07001354 def FullOTA_InstallEnd(self):
1355 """Called at the end of full OTA installation; typically this is
1356 used to install the image for the device's baseband processor."""
1357 return self._DoCall("FullOTA_InstallEnd")
1358
1359 def IncrementalOTA_Assertions(self):
1360 """Called after emitting the block of assertions at the top of an
1361 incremental OTA package. Implementations can add whatever
1362 additional assertions they like."""
1363 return self._DoCall("IncrementalOTA_Assertions")
1364
Doug Zongkere5ff5902012-01-17 10:55:37 -08001365 def IncrementalOTA_VerifyBegin(self):
1366 """Called at the start of the verification phase of incremental
1367 OTA installation; additional checks can be placed here to abort
1368 the script before any changes are made."""
1369 return self._DoCall("IncrementalOTA_VerifyBegin")
1370
Doug Zongker05d3dea2009-06-22 11:32:31 -07001371 def IncrementalOTA_VerifyEnd(self):
1372 """Called at the end of the verification phase of incremental OTA
1373 installation; additional checks can be placed here to abort the
1374 script before any changes are made."""
1375 return self._DoCall("IncrementalOTA_VerifyEnd")
1376
Doug Zongkere5ff5902012-01-17 10:55:37 -08001377 def IncrementalOTA_InstallBegin(self):
1378 """Called at the start of incremental OTA installation (after
1379 verification is complete)."""
1380 return self._DoCall("IncrementalOTA_InstallBegin")
1381
Doug Zongker05d3dea2009-06-22 11:32:31 -07001382 def IncrementalOTA_InstallEnd(self):
1383 """Called at the end of incremental OTA installation; typically
1384 this is used to install the image for the device's baseband
1385 processor."""
1386 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001387
Tao Bao9bc6bb22015-11-09 16:58:28 -08001388 def VerifyOTA_Assertions(self):
1389 return self._DoCall("VerifyOTA_Assertions")
1390
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001391class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001392 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001393 self.name = name
1394 self.data = data
1395 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001396 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001397 self.sha1 = sha1(data).hexdigest()
1398
1399 @classmethod
1400 def FromLocalFile(cls, name, diskname):
1401 f = open(diskname, "rb")
1402 data = f.read()
1403 f.close()
1404 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001405
1406 def WriteToTemp(self):
1407 t = tempfile.NamedTemporaryFile()
1408 t.write(self.data)
1409 t.flush()
1410 return t
1411
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001412 def WriteToDir(self, d):
1413 with open(os.path.join(d, self.name), "wb") as fp:
1414 fp.write(self.data)
1415
Geremy Condra36bd3652014-02-06 19:45:10 -08001416 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001417 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001418
1419DIFF_PROGRAM_BY_EXT = {
1420 ".gz" : "imgdiff",
1421 ".zip" : ["imgdiff", "-z"],
1422 ".jar" : ["imgdiff", "-z"],
1423 ".apk" : ["imgdiff", "-z"],
1424 ".img" : "imgdiff",
1425 }
1426
1427class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001428 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001429 self.tf = tf
1430 self.sf = sf
1431 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001432 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001433
1434 def ComputePatch(self):
1435 """Compute the patch (as a string of data) needed to turn sf into
1436 tf. Returns the same tuple as GetPatch()."""
1437
1438 tf = self.tf
1439 sf = self.sf
1440
Doug Zongker24cd2802012-08-14 16:36:15 -07001441 if self.diff_program:
1442 diff_program = self.diff_program
1443 else:
1444 ext = os.path.splitext(tf.name)[1]
1445 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001446
1447 ttemp = tf.WriteToTemp()
1448 stemp = sf.WriteToTemp()
1449
1450 ext = os.path.splitext(tf.name)[1]
1451
1452 try:
1453 ptemp = tempfile.NamedTemporaryFile()
1454 if isinstance(diff_program, list):
1455 cmd = copy.copy(diff_program)
1456 else:
1457 cmd = [diff_program]
1458 cmd.append(stemp.name)
1459 cmd.append(ttemp.name)
1460 cmd.append(ptemp.name)
1461 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001462 err = []
1463 def run():
1464 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001465 if e:
1466 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001467 th = threading.Thread(target=run)
1468 th.start()
1469 th.join(timeout=300) # 5 mins
1470 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001471 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001472 p.terminate()
1473 th.join(5)
1474 if th.is_alive():
1475 p.kill()
1476 th.join()
1477
Tianjie Xua2a9f992018-01-05 15:15:54 -08001478 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001479 print("WARNING: failure running %s:\n%s\n" % (
1480 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001481 self.patch = None
1482 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001483 diff = ptemp.read()
1484 finally:
1485 ptemp.close()
1486 stemp.close()
1487 ttemp.close()
1488
1489 self.patch = diff
1490 return self.tf, self.sf, self.patch
1491
1492
1493 def GetPatch(self):
1494 """Return a tuple (target_file, source_file, patch_data).
1495 patch_data may be None if ComputePatch hasn't been called, or if
1496 computing the patch failed."""
1497 return self.tf, self.sf, self.patch
1498
1499
1500def ComputeDifferences(diffs):
1501 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001502 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001503
1504 # Do the largest files first, to try and reduce the long-pole effect.
1505 by_size = [(i.tf.size, i) for i in diffs]
1506 by_size.sort(reverse=True)
1507 by_size = [i[1] for i in by_size]
1508
1509 lock = threading.Lock()
1510 diff_iter = iter(by_size) # accessed under lock
1511
1512 def worker():
1513 try:
1514 lock.acquire()
1515 for d in diff_iter:
1516 lock.release()
1517 start = time.time()
1518 d.ComputePatch()
1519 dur = time.time() - start
1520 lock.acquire()
1521
1522 tf, sf, patch = d.GetPatch()
1523 if sf.name == tf.name:
1524 name = tf.name
1525 else:
1526 name = "%s (%s)" % (tf.name, sf.name)
1527 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001528 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001529 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001530 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1531 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001532 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001533 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001534 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001535 raise
1536
1537 # start worker threads; wait for them all to finish.
1538 threads = [threading.Thread(target=worker)
1539 for i in range(OPTIONS.worker_threads)]
1540 for th in threads:
1541 th.start()
1542 while threads:
1543 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001544
1545
Dan Albert8b72aef2015-03-23 19:13:21 -07001546class BlockDifference(object):
1547 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001548 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001549 self.tgt = tgt
1550 self.src = src
1551 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001552 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001553 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001554
Tao Baodd2a5892015-03-12 12:32:37 -07001555 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001556 version = max(
1557 int(i) for i in
1558 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001559 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001560 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001561
1562 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001563 version=self.version,
1564 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001565 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001566 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001567 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001568 self.touched_src_ranges = b.touched_src_ranges
1569 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001570
Tao Baoaac4ad52015-10-16 15:26:34 -07001571 if src is None:
1572 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1573 else:
1574 _, self.device = GetTypeAndDevice("/" + partition,
1575 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001576
Tao Baod8d14be2016-02-04 14:26:02 -08001577 @property
1578 def required_cache(self):
1579 return self._required_cache
1580
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001581 def WriteScript(self, script, output_zip, progress=None):
1582 if not self.src:
1583 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001584 script.Print("Patching %s image unconditionally..." % (self.partition,))
1585 else:
1586 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001587
Dan Albert8b72aef2015-03-23 19:13:21 -07001588 if progress:
1589 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001590 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001591 if OPTIONS.verify:
1592 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001593
Tao Bao9bc6bb22015-11-09 16:58:28 -08001594 def WriteStrictVerifyScript(self, script):
1595 """Verify all the blocks in the care_map, including clobbered blocks.
1596
1597 This differs from the WriteVerifyScript() function: a) it prints different
1598 error messages; b) it doesn't allow half-way updated images to pass the
1599 verification."""
1600
1601 partition = self.partition
1602 script.Print("Verifying %s..." % (partition,))
1603 ranges = self.tgt.care_map
1604 ranges_str = ranges.to_string_raw()
1605 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1606 'ui_print(" Verified.") || '
1607 'ui_print("\\"%s\\" has unexpected contents.");' % (
1608 self.device, ranges_str,
1609 self.tgt.TotalSha1(include_clobbered_blocks=True),
1610 self.device))
1611 script.AppendExtra("")
1612
Tao Baod522bdc2016-04-12 15:53:16 -07001613 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001614 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001615
1616 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001617 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001618 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001619
1620 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001621 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001622 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001623 ranges = self.touched_src_ranges
1624 expected_sha1 = self.touched_src_sha1
1625 else:
1626 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1627 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001628
1629 # No blocks to be checked, skipping.
1630 if not ranges:
1631 return
1632
Tao Bao5ece99d2015-05-12 11:42:31 -07001633 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001634 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1635 'block_image_verify("%s", '
1636 'package_extract_file("%s.transfer.list"), '
1637 '"%s.new.dat", "%s.patch.dat")) then') % (
1638 self.device, ranges_str, expected_sha1,
1639 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001640 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001641 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001642
Tianjie Xufc3422a2015-12-15 11:53:59 -08001643 if self.version >= 4:
1644
1645 # Bug: 21124327
1646 # When generating incrementals for the system and vendor partitions in
1647 # version 4 or newer, explicitly check the first block (which contains
1648 # the superblock) of the partition to see if it's what we expect. If
1649 # this check fails, give an explicit log message about the partition
1650 # having been remounted R/W (the most likely explanation).
1651 if self.check_first_block:
1652 script.AppendExtra('check_first_block("%s");' % (self.device,))
1653
1654 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001655 if partition == "system":
1656 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1657 else:
1658 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001659 script.AppendExtra((
1660 'ifelse (block_image_recover("{device}", "{ranges}") && '
1661 'block_image_verify("{device}", '
1662 'package_extract_file("{partition}.transfer.list"), '
1663 '"{partition}.new.dat", "{partition}.patch.dat"), '
1664 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001665 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001666 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001667 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001668
Tao Baodd2a5892015-03-12 12:32:37 -07001669 # Abort the OTA update. Note that the incremental OTA cannot be applied
1670 # even if it may match the checksum of the target partition.
1671 # a) If version < 3, operations like move and erase will make changes
1672 # unconditionally and damage the partition.
1673 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001674 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001675 if partition == "system":
1676 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1677 else:
1678 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1679 script.AppendExtra((
1680 'abort("E%d: %s partition has unexpected contents");\n'
1681 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001682
Tao Bao5fcaaef2015-06-01 13:40:49 -07001683 def _WritePostInstallVerifyScript(self, script):
1684 partition = self.partition
1685 script.Print('Verifying the updated %s image...' % (partition,))
1686 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1687 ranges = self.tgt.care_map
1688 ranges_str = ranges.to_string_raw()
1689 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1690 self.device, ranges_str,
1691 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001692
1693 # Bug: 20881595
1694 # Verify that extended blocks are really zeroed out.
1695 if self.tgt.extended:
1696 ranges_str = self.tgt.extended.to_string_raw()
1697 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1698 self.device, ranges_str,
1699 self._HashZeroBlocks(self.tgt.extended.size())))
1700 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001701 if partition == "system":
1702 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1703 else:
1704 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001705 script.AppendExtra(
1706 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001707 ' abort("E%d: %s partition has unexpected non-zero contents after '
1708 'OTA update");\n'
1709 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001710 else:
1711 script.Print('Verified the updated %s image.' % (partition,))
1712
Tianjie Xu209db462016-05-24 17:34:52 -07001713 if partition == "system":
1714 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1715 else:
1716 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1717
Tao Bao5fcaaef2015-06-01 13:40:49 -07001718 script.AppendExtra(
1719 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001720 ' abort("E%d: %s partition has unexpected contents after OTA '
1721 'update");\n'
1722 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001723
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001724 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001725 ZipWrite(output_zip,
1726 '{}.transfer.list'.format(self.path),
1727 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001728
1729 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1730 # almost triples the compression time but doesn't further reduce the size too much.
1731 # For a typical 1.8G system.new.dat
1732 # zip | brotli(quality 6) | brotli(quality 9)
1733 # compressed_size: 942M | 869M (~8% reduced) | 854M
1734 # compression_time: 75s | 265s | 719s
1735 # decompression_time: 15s | 25s | 25s
1736
1737 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001738 brotli_cmd = ['brotli', '--quality=6',
1739 '--output={}.new.dat.br'.format(self.path),
1740 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001741 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao80921982018-03-21 21:02:19 -07001742 p = Run(brotli_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1743 stdoutdata, _ = p.communicate()
1744 assert p.returncode == 0, \
1745 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1746 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001747
1748 new_data_name = '{}.new.dat.br'.format(self.partition)
1749 ZipWrite(output_zip,
1750 '{}.new.dat.br'.format(self.path),
1751 new_data_name,
1752 compress_type=zipfile.ZIP_STORED)
1753 else:
1754 new_data_name = '{}.new.dat'.format(self.partition)
1755 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1756
Dan Albert8e0178d2015-01-27 15:53:15 -08001757 ZipWrite(output_zip,
1758 '{}.patch.dat'.format(self.path),
1759 '{}.patch.dat'.format(self.partition),
1760 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001761
Tianjie Xu209db462016-05-24 17:34:52 -07001762 if self.partition == "system":
1763 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1764 else:
1765 code = ErrorCode.VENDOR_UPDATE_FAILURE
1766
Dan Albert8e0178d2015-01-27 15:53:15 -08001767 call = ('block_image_update("{device}", '
1768 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001769 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001770 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001771 device=self.device, partition=self.partition,
1772 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001773 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001774
Dan Albert8b72aef2015-03-23 19:13:21 -07001775 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001776 data = source.ReadRangeSet(ranges)
1777 ctx = sha1()
1778
1779 for p in data:
1780 ctx.update(p)
1781
1782 return ctx.hexdigest()
1783
Tao Baoe9b61912015-07-09 17:37:49 -07001784 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1785 """Return the hash value for all zero blocks."""
1786 zero_block = '\x00' * 4096
1787 ctx = sha1()
1788 for _ in range(num_blocks):
1789 ctx.update(zero_block)
1790
1791 return ctx.hexdigest()
1792
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001793
1794DataImage = blockimgdiff.DataImage
1795
Doug Zongker96a57e72010-09-26 14:57:41 -07001796# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001797PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001798 "ext4": "EMMC",
1799 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001800 "f2fs": "EMMC",
1801 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001802}
Doug Zongker96a57e72010-09-26 14:57:41 -07001803
1804def GetTypeAndDevice(mount_point, info):
1805 fstab = info["fstab"]
1806 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001807 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1808 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001809 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001810 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001811
1812
1813def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001814 """Parses and converts a PEM-encoded certificate into DER-encoded.
1815
1816 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1817
1818 Returns:
1819 The decoded certificate string.
1820 """
1821 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001822 save = False
1823 for line in data.split("\n"):
1824 if "--END CERTIFICATE--" in line:
1825 break
1826 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001827 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001828 if "--BEGIN CERTIFICATE--" in line:
1829 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001830 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001831 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001832
Tao Bao04e1f012018-02-04 12:13:35 -08001833
1834def ExtractPublicKey(cert):
1835 """Extracts the public key (PEM-encoded) from the given certificate file.
1836
1837 Args:
1838 cert: The certificate filename.
1839
1840 Returns:
1841 The public key string.
1842
1843 Raises:
1844 AssertionError: On non-zero return from 'openssl'.
1845 """
1846 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1847 # While openssl 1.1 writes the key into the given filename followed by '-out',
1848 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1849 # stdout instead.
1850 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1851 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1852 pubkey, stderrdata = proc.communicate()
1853 assert proc.returncode == 0, \
1854 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1855 return pubkey
1856
1857
Doug Zongker412c02f2014-02-13 10:58:24 -08001858def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1859 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001860 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001861
Tao Bao6d5d6232018-03-09 17:04:42 -08001862 Most of the space in the boot and recovery images is just the kernel, which is
1863 identical for the two, so the resulting patch should be efficient. Add it to
1864 the output zip, along with a shell script that is run from init.rc on first
1865 boot to actually do the patching and install the new recovery image.
1866
1867 Args:
1868 input_dir: The top-level input directory of the target-files.zip.
1869 output_sink: The callback function that writes the result.
1870 recovery_img: File object for the recovery image.
1871 boot_img: File objects for the boot image.
1872 info_dict: A dict returned by common.LoadInfoDict() on the input
1873 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001874 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001875 if info_dict is None:
1876 info_dict = OPTIONS.info_dict
1877
Tao Bao6d5d6232018-03-09 17:04:42 -08001878 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001879
Tao Baof2cffbd2015-07-22 12:33:18 -07001880 if full_recovery_image:
1881 output_sink("etc/recovery.img", recovery_img.data)
1882
1883 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08001884 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07001885 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08001886 # With system-root-image, boot and recovery images will have mismatching
1887 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
1888 # to handle such a case.
1889 if system_root_image:
1890 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07001891 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08001892 assert not os.path.exists(path)
1893 else:
1894 diff_program = ["imgdiff"]
1895 if os.path.exists(path):
1896 diff_program.append("-b")
1897 diff_program.append(path)
1898 bonus_args = "-b /system/etc/recovery-resource.dat"
1899 else:
1900 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07001901
1902 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1903 _, _, patch = d.ComputePatch()
1904 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001905
Dan Albertebb19aa2015-03-27 19:11:53 -07001906 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001907 # The following GetTypeAndDevice()s need to use the path in the target
1908 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001909 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1910 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1911 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001912 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001913
Tao Baof2cffbd2015-07-22 12:33:18 -07001914 if full_recovery_image:
1915 sh = """#!/system/bin/sh
1916if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1917 applypatch /system/etc/recovery.img %(type)s:%(device)s %(sha1)s %(size)d && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1918else
1919 log -t recovery "Recovery image already installed"
1920fi
1921""" % {'type': recovery_type,
1922 'device': recovery_device,
1923 'sha1': recovery_img.sha1,
1924 'size': recovery_img.size}
1925 else:
1926 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001927if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1928 applypatch %(bonus_args)s %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1929else
1930 log -t recovery "Recovery image already installed"
1931fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001932""" % {'boot_size': boot_img.size,
1933 'boot_sha1': boot_img.sha1,
1934 'recovery_size': recovery_img.size,
1935 'recovery_sha1': recovery_img.sha1,
1936 'boot_type': boot_type,
1937 'boot_device': boot_device,
1938 'recovery_type': recovery_type,
1939 'recovery_device': recovery_device,
1940 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001941
1942 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001943 # in the L release.
1944 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001945
Tao Bao89fbb0f2017-01-10 10:47:58 -08001946 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001947
1948 output_sink(sh_location, sh)