blob: c048e0fc8a18d8bdddcf4e63ae4058f7c19b5de4 [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
Dan Albert8b72aef2015-03-23 19:13:21 -0700112class ExternalError(RuntimeError):
113 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700114
115
Tao Bao39451582017-05-04 11:10:47 -0700116def Run(args, verbose=None, **kwargs):
117 """Create and return a subprocess.Popen object.
118
119 Caller can specify if the command line should be printed. The global
120 OPTIONS.verbose will be used if not specified.
121 """
122 if verbose is None:
123 verbose = OPTIONS.verbose
124 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800125 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700126 return subprocess.Popen(args, **kwargs)
127
128
Tao Baoc765cca2018-01-31 17:32:40 -0800129def RoundUpTo4K(value):
130 rounded_up = value + 4095
131 return rounded_up - (rounded_up % 4096)
132
133
Ying Wang7e6d4e42010-12-13 16:25:36 -0800134def CloseInheritedPipes():
135 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
136 before doing other work."""
137 if platform.system() != "Darwin":
138 return
139 for d in range(3, 1025):
140 try:
141 stat = os.fstat(d)
142 if stat is not None:
143 pipebit = stat[0] & 0x1000
144 if pipebit != 0:
145 os.close(d)
146 except OSError:
147 pass
148
149
Tao Bao2c15d9e2015-07-09 11:51:16 -0700150def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700151 """Read and parse the META/misc_info.txt key/value pairs from the
152 input target files and return a dict."""
153
Doug Zongkerc9253822014-02-04 12:17:58 -0800154 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700155 if isinstance(input_file, zipfile.ZipFile):
156 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800157 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700158 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800159 try:
160 with open(path) as f:
161 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700162 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800163 if e.errno == errno.ENOENT:
164 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800165
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700166 try:
Michael Runge6e836112014-04-15 17:40:21 -0700167 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700168 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800169 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700170
Tao Bao6cd54732017-02-27 15:12:05 -0800171 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800172 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800173
Tao Bao84e75682015-07-19 02:38:53 -0700174 # A few properties are stored as links to the files in the out/ directory.
175 # It works fine with the build system. However, they are no longer available
176 # when (re)generating from target_files zip. If input_dir is not None, we
177 # are doing repacking. Redirect those properties to the actual files in the
178 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700179 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400180 # We carry a copy of file_contexts.bin under META/. If not available,
181 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700182 # to build images than the one running on device, such as when enabling
183 # system_root_image. In that case, we must have the one for image
184 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700185 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
186 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700187 if d.get("system_root_image") == "true":
188 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700189 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700190 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700191 if not os.path.exists(fc_config):
192 fc_config = None
193
194 if fc_config:
195 d["selinux_fc"] = fc_config
196
Tao Bao84e75682015-07-19 02:38:53 -0700197 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
198 if d.get("system_root_image") == "true":
199 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
200 d["ramdisk_fs_config"] = os.path.join(
201 input_dir, "META", "root_filesystem_config.txt")
202
Tao Baof54216f2016-03-29 15:12:37 -0700203 # Redirect {system,vendor}_base_fs_file.
204 if "system_base_fs_file" in d:
205 basename = os.path.basename(d["system_base_fs_file"])
206 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700207 if os.path.exists(system_base_fs_file):
208 d["system_base_fs_file"] = system_base_fs_file
209 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800210 print("Warning: failed to find system base fs file: %s" % (
211 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700212 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700213
214 if "vendor_base_fs_file" in d:
215 basename = os.path.basename(d["vendor_base_fs_file"])
216 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700217 if os.path.exists(vendor_base_fs_file):
218 d["vendor_base_fs_file"] = vendor_base_fs_file
219 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800220 print("Warning: failed to find vendor base fs file: %s" % (
221 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700222 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700223
Doug Zongker37974732010-09-16 17:44:38 -0700224 def makeint(key):
225 if key in d:
226 d[key] = int(d[key], 0)
227
228 makeint("recovery_api_version")
229 makeint("blocksize")
230 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700231 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700232 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700233 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700234 makeint("recovery_size")
235 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800236 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700237
Tianjie Xucfa86222016-03-07 16:31:19 -0800238 system_root_image = d.get("system_root_image", None) == "true"
239 if d.get("no_recovery", None) != "true":
240 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800241 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800242 recovery_fstab_path, system_root_image)
243 elif d.get("recovery_as_boot", None) == "true":
244 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
245 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
246 recovery_fstab_path, system_root_image)
247 else:
248 d["fstab"] = None
249
Tao Baobcd1d162017-08-26 13:10:26 -0700250 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
251 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Tao Bao12d87fc2018-01-31 12:18:52 -0800252
253 # Set up the salt (based on fingerprint or thumbprint) that will be used when
254 # adding AVB footer.
255 if d.get("avb_enable") == "true":
256 fp = None
257 if "build.prop" in d:
258 build_prop = d["build.prop"]
259 if "ro.build.fingerprint" in build_prop:
260 fp = build_prop["ro.build.fingerprint"]
261 elif "ro.build.thumbprint" in build_prop:
262 fp = build_prop["ro.build.thumbprint"]
263 if fp:
264 d["avb_salt"] = sha256(fp).hexdigest()
265
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700266 return d
267
Tao Baod1de6f32017-03-01 16:38:48 -0800268
Tao Baobcd1d162017-08-26 13:10:26 -0700269def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700270 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700271 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700272 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700273 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700274 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700275 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700276
Tao Baod1de6f32017-03-01 16:38:48 -0800277
Michael Runge6e836112014-04-15 17:40:21 -0700278def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700279 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700280 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700281 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700282 if not line or line.startswith("#"):
283 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700284 if "=" in line:
285 name, value = line.split("=", 1)
286 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700287 return d
288
Tao Baod1de6f32017-03-01 16:38:48 -0800289
Tianjie Xucfa86222016-03-07 16:31:19 -0800290def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
291 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700292 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800293 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700294 self.mount_point = mount_point
295 self.fs_type = fs_type
296 self.device = device
297 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700298 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700299
300 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800301 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700302 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800303 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700304 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700305
Tao Baod1de6f32017-03-01 16:38:48 -0800306 assert fstab_version == 2
307
308 d = {}
309 for line in data.split("\n"):
310 line = line.strip()
311 if not line or line.startswith("#"):
312 continue
313
314 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
315 pieces = line.split()
316 if len(pieces) != 5:
317 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
318
319 # Ignore entries that are managed by vold.
320 options = pieces[4]
321 if "voldmanaged=" in options:
322 continue
323
324 # It's a good line, parse it.
325 length = 0
326 options = options.split(",")
327 for i in options:
328 if i.startswith("length="):
329 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800330 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800331 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700332 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800333
Tao Baod1de6f32017-03-01 16:38:48 -0800334 mount_flags = pieces[3]
335 # Honor the SELinux context if present.
336 context = None
337 for i in mount_flags.split(","):
338 if i.startswith("context="):
339 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800340
Tao Baod1de6f32017-03-01 16:38:48 -0800341 mount_point = pieces[1]
342 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
343 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800344
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700345 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700346 # system. Other areas assume system is always at "/system" so point /system
347 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700348 if system_root_image:
349 assert not d.has_key("/system") and d.has_key("/")
350 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700351 return d
352
353
Doug Zongker37974732010-09-16 17:44:38 -0700354def DumpInfoDict(d):
355 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800356 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700357
Dan Albert8b72aef2015-03-23 19:13:21 -0700358
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800359def AppendAVBSigningArgs(cmd, partition):
360 """Append signing arguments for avbtool."""
361 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
362 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
363 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
364 if key_path and algorithm:
365 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700366 avb_salt = OPTIONS.info_dict.get("avb_salt")
367 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
368 if avb_salt and partition != "vbmeta":
369 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800370
371
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700372def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800373 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700374 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700375
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700376 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800377 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
378 we are building a two-step special image (i.e. building a recovery image to
379 be loaded into /boot in two-step OTAs).
380
381 Return the image data, or None if sourcedir does not appear to contains files
382 for building the requested image.
383 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700384
385 def make_ramdisk():
386 ramdisk_img = tempfile.NamedTemporaryFile()
387
388 if os.access(fs_config_file, os.F_OK):
389 cmd = ["mkbootfs", "-f", fs_config_file,
390 os.path.join(sourcedir, "RAMDISK")]
391 else:
392 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
393 p1 = Run(cmd, stdout=subprocess.PIPE)
394 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
395
396 p2.wait()
397 p1.wait()
398 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
399 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
400
401 return ramdisk_img
402
403 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
404 return None
405
406 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700407 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700408
Doug Zongkerd5131602012-08-02 14:46:42 -0700409 if info_dict is None:
410 info_dict = OPTIONS.info_dict
411
Doug Zongkereef39442009-04-02 12:14:19 -0700412 img = tempfile.NamedTemporaryFile()
413
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700414 if has_ramdisk:
415 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700416
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800417 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
418 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
419
420 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700421
Benoit Fradina45a8682014-07-14 21:00:43 +0200422 fn = os.path.join(sourcedir, "second")
423 if os.access(fn, os.F_OK):
424 cmd.append("--second")
425 cmd.append(fn)
426
Doug Zongker171f1cd2009-06-15 22:36:37 -0700427 fn = os.path.join(sourcedir, "cmdline")
428 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700429 cmd.append("--cmdline")
430 cmd.append(open(fn).read().rstrip("\n"))
431
432 fn = os.path.join(sourcedir, "base")
433 if os.access(fn, os.F_OK):
434 cmd.append("--base")
435 cmd.append(open(fn).read().rstrip("\n"))
436
Ying Wang4de6b5b2010-08-25 14:29:34 -0700437 fn = os.path.join(sourcedir, "pagesize")
438 if os.access(fn, os.F_OK):
439 cmd.append("--pagesize")
440 cmd.append(open(fn).read().rstrip("\n"))
441
Doug Zongkerd5131602012-08-02 14:46:42 -0700442 args = info_dict.get("mkbootimg_args", None)
443 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700444 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700445
Sami Tolvanen3303d902016-03-15 16:49:30 +0000446 args = info_dict.get("mkbootimg_version_args", None)
447 if args and args.strip():
448 cmd.extend(shlex.split(args))
449
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700450 if has_ramdisk:
451 cmd.extend(["--ramdisk", ramdisk_img.name])
452
Tao Baod95e9fd2015-03-29 23:07:41 -0700453 img_unsigned = None
454 if info_dict.get("vboot", None):
455 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700456 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700457 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700458 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700459
Tao Baobf70c3182017-07-11 17:27:55 -0700460 # "boot" or "recovery", without extension.
461 partition_name = os.path.basename(sourcedir).lower()
462
Doug Zongker38a649f2009-06-17 09:07:09 -0700463 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700464 p.communicate()
Tao Baobf70c3182017-07-11 17:27:55 -0700465 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700466
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100467 if (info_dict.get("boot_signer", None) == "true" and
468 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800469 # Hard-code the path as "/boot" for two-step special recovery image (which
470 # will be loaded into /boot during the two-step OTA).
471 if two_step_image:
472 path = "/boot"
473 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700474 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700475 cmd = [OPTIONS.boot_signer_path]
476 cmd.extend(OPTIONS.boot_signer_args)
477 cmd.extend([path, img.name,
478 info_dict["verity_key"] + ".pk8",
479 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700480 p = Run(cmd, stdout=subprocess.PIPE)
481 p.communicate()
482 assert p.returncode == 0, "boot_signer of %s image failed" % path
483
Tao Baod95e9fd2015-03-29 23:07:41 -0700484 # Sign the image if vboot is non-empty.
485 elif info_dict.get("vboot", None):
Tao Baobf70c3182017-07-11 17:27:55 -0700486 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700487 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800488 # We have switched from the prebuilt futility binary to using the tool
489 # (futility-host) built from the source. Override the setting in the old
490 # TF.zip.
491 futility = info_dict["futility"]
492 if futility.startswith("prebuilts/"):
493 futility = "futility-host"
494 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700495 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700496 info_dict["vboot_key"] + ".vbprivk",
497 info_dict["vboot_subkey"] + ".vbprivk",
498 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700499 img.name]
500 p = Run(cmd, stdout=subprocess.PIPE)
501 p.communicate()
502 assert p.returncode == 0, "vboot_signer of %s image failed" % path
503
Tao Baof3282b42015-04-01 11:21:55 -0700504 # Clean up the temp files.
505 img_unsigned.close()
506 img_keyblock.close()
507
David Zeuthen8fecb282017-12-01 16:24:01 -0500508 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800509 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700510 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500511 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400512 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700513 "--partition_size", str(part_size), "--partition_name",
514 partition_name]
515 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500516 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400517 if args and args.strip():
518 cmd.extend(shlex.split(args))
519 p = Run(cmd, stdout=subprocess.PIPE)
520 p.communicate()
521 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c3182017-07-11 17:27:55 -0700522 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500523
524 img.seek(os.SEEK_SET, 0)
525 data = img.read()
526
527 if has_ramdisk:
528 ramdisk_img.close()
529 img.close()
530
531 return data
532
533
Doug Zongkerd5131602012-08-02 14:46:42 -0700534def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800535 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700536 """Return a File object with the desired bootable image.
537
538 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
539 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
540 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700541
Doug Zongker55d93282011-01-25 17:03:34 -0800542 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
543 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800544 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800545 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700546
547 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
548 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800549 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700550 return File.FromLocalFile(name, prebuilt_path)
551
Tao Bao89fbb0f2017-01-10 10:47:58 -0800552 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700553
554 if info_dict is None:
555 info_dict = OPTIONS.info_dict
556
557 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800558 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
559 # for recovery.
560 has_ramdisk = (info_dict.get("system_root_image") != "true" or
561 prebuilt_name != "boot.img" or
562 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700563
Doug Zongker6f1d0312014-08-22 08:07:12 -0700564 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400565 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
566 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800567 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700568 if data:
569 return File(name, data)
570 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800571
Doug Zongkereef39442009-04-02 12:14:19 -0700572
Narayan Kamatha07bf042017-08-14 14:49:21 +0100573def Gunzip(in_filename, out_filename):
574 """Gunzip the given gzip compressed file to a given output file.
575 """
576 with gzip.open(in_filename, "rb") as in_file, open(out_filename, "wb") as out_file:
577 shutil.copyfileobj(in_file, out_file)
578
579
Doug Zongker75f17362009-12-08 13:46:44 -0800580def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800581 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800582
Tao Bao1c830bf2017-12-25 10:43:47 -0800583 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
584 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800585
Tao Bao1c830bf2017-12-25 10:43:47 -0800586 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800587 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800588 """
Doug Zongkereef39442009-04-02 12:14:19 -0700589
Doug Zongker55d93282011-01-25 17:03:34 -0800590 def unzip_to_dir(filename, dirname):
591 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
592 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800593 cmd.extend(pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800594 p = Run(cmd, stdout=subprocess.PIPE)
595 p.communicate()
596 if p.returncode != 0:
597 raise ExternalError("failed to unzip input target-files \"%s\"" %
598 (filename,))
599
Tao Bao1c830bf2017-12-25 10:43:47 -0800600 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800601 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
602 if m:
603 unzip_to_dir(m.group(1), tmp)
604 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
605 filename = m.group(1)
606 else:
607 unzip_to_dir(filename, tmp)
608
Tao Baodba59ee2018-01-09 13:21:02 -0800609 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700610
611
Tao Baoe709b092018-02-07 12:40:00 -0800612def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -0800613 """Returns a SparseImage object suitable for passing to BlockImageDiff.
614
615 This function loads the specified sparse image from the given path, and
616 performs additional processing for OTA purpose. For example, it always adds
617 block 0 to clobbered blocks list. It also detects files that cannot be
618 reconstructed from the block list, for whom we should avoid applying imgdiff.
619
620 Args:
621 which: The partition name, which must be "system" or "vendor".
622 tmpdir: The directory that contains the prebuilt image and block map file.
623 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800624 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -0800625
626 Returns:
627 A SparseImage object, with file_map info loaded.
628 """
629 assert which in ("system", "vendor")
630
631 path = os.path.join(tmpdir, "IMAGES", which + ".img")
632 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
633
634 # The image and map files must have been created prior to calling
635 # ota_from_target_files.py (since LMP).
636 assert os.path.exists(path) and os.path.exists(mappath)
637
638 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
639 # it to clobbered_blocks so that it will be written to the target
640 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
641 clobbered_blocks = "0"
642
Tao Baoe709b092018-02-07 12:40:00 -0800643 image = sparse_img.SparseImage(path, mappath, clobbered_blocks,
644 allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -0800645
646 # block.map may contain less blocks, because mke2fs may skip allocating blocks
647 # if they contain all zeros. We can't reconstruct such a file from its block
648 # list. Tag such entries accordingly. (Bug: 65213616)
649 for entry in image.file_map:
650 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar".
651 arcname = string.replace(entry, which, which.upper(), 1)[1:]
652 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
653 if arcname not in input_zip.namelist():
654 continue
655
656 info = input_zip.getinfo(arcname)
657 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800658
659 # If a RangeSet has been tagged as using shared blocks while loading the
660 # image, its block list must be already incomplete due to that reason. Don't
661 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
662 if ranges.extra.get('uses_shared_blocks'):
663 continue
664
Tao Baoc765cca2018-01-31 17:32:40 -0800665 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
666 ranges.extra['incomplete'] = True
667
668 return image
669
670
Doug Zongkereef39442009-04-02 12:14:19 -0700671def GetKeyPasswords(keylist):
672 """Given a list of keys, prompt the user to enter passwords for
673 those which require them. Return a {key: password} dict. password
674 will be None if the key has no password."""
675
Doug Zongker8ce7c252009-05-22 13:34:54 -0700676 no_passwords = []
677 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700678 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700679 devnull = open("/dev/null", "w+b")
680 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800681 # We don't need a password for things that aren't really keys.
682 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700683 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700684 continue
685
T.R. Fullhart37e10522013-03-18 10:31:26 -0700686 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700687 "-inform", "DER", "-nocrypt"],
688 stdin=devnull.fileno(),
689 stdout=devnull.fileno(),
690 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700691 p.communicate()
692 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700693 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700694 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700695 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700696 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
697 "-inform", "DER", "-passin", "pass:"],
698 stdin=devnull.fileno(),
699 stdout=devnull.fileno(),
700 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700701 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700702 if p.returncode == 0:
703 # Encrypted key with empty string as password.
704 key_passwords[k] = ''
705 elif stderr.startswith('Error decrypting key'):
706 # Definitely encrypted key.
707 # It would have said "Error reading key" if it didn't parse correctly.
708 need_passwords.append(k)
709 else:
710 # Potentially, a type of key that openssl doesn't understand.
711 # We'll let the routines in signapk.jar handle it.
712 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700713 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700714
T.R. Fullhart37e10522013-03-18 10:31:26 -0700715 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700716 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700717 return key_passwords
718
719
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800720def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700721 """Gets the minSdkVersion declared in the APK.
722
723 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
724 This can be both a decimal number (API Level) or a codename.
725
726 Args:
727 apk_name: The APK filename.
728
729 Returns:
730 The parsed SDK version string.
731
732 Raises:
733 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800734 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700735 proc = Run(
736 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
737 stderr=subprocess.PIPE)
738 stdoutdata, stderrdata = proc.communicate()
739 if proc.returncode != 0:
740 raise ExternalError(
741 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
742 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800743
Tao Baof47bf0f2018-03-21 23:28:51 -0700744 for line in stdoutdata.split("\n"):
745 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800746 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
747 if m:
748 return m.group(1)
749 raise ExternalError("No minSdkVersion returned by aapt")
750
751
752def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700753 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800754
Tao Baof47bf0f2018-03-21 23:28:51 -0700755 If minSdkVersion is set to a codename, it is translated to a number using the
756 provided map.
757
758 Args:
759 apk_name: The APK filename.
760
761 Returns:
762 The parsed SDK version number.
763
764 Raises:
765 ExternalError: On failing to get the min SDK version number.
766 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800767 version = GetMinSdkVersion(apk_name)
768 try:
769 return int(version)
770 except ValueError:
771 # Not a decimal number. Codename?
772 if version in codename_to_api_level_map:
773 return codename_to_api_level_map[version]
774 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700775 raise ExternalError(
776 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
777 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800778
779
780def SignFile(input_name, output_name, key, password, min_api_level=None,
781 codename_to_api_level_map=dict(),
782 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700783 """Sign the input_name zip/jar/apk, producing output_name. Use the
784 given key and password (the latter may be None if the key does not
785 have a password.
786
Doug Zongker951495f2009-08-14 12:44:19 -0700787 If whole_file is true, use the "-w" option to SignApk to embed a
788 signature that covers the whole file in the archive comment of the
789 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800790
791 min_api_level is the API Level (int) of the oldest platform this file may end
792 up on. If not specified for an APK, the API Level is obtained by interpreting
793 the minSdkVersion attribute of the APK's AndroidManifest.xml.
794
795 codename_to_api_level_map is needed to translate the codename which may be
796 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700797 """
Doug Zongker951495f2009-08-14 12:44:19 -0700798
Alex Klyubin9667b182015-12-10 13:38:50 -0800799 java_library_path = os.path.join(
800 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
801
Tao Baoe95540e2016-11-08 12:08:53 -0800802 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
803 ["-Djava.library.path=" + java_library_path,
804 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
805 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700806 if whole_file:
807 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800808
809 min_sdk_version = min_api_level
810 if min_sdk_version is None:
811 if not whole_file:
812 min_sdk_version = GetMinSdkVersionInt(
813 input_name, codename_to_api_level_map)
814 if min_sdk_version is not None:
815 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
816
T.R. Fullhart37e10522013-03-18 10:31:26 -0700817 cmd.extend([key + OPTIONS.public_key_suffix,
818 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800819 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700820
821 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700822 if password is not None:
823 password += "\n"
824 p.communicate(password)
825 if p.returncode != 0:
826 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
827
Doug Zongkereef39442009-04-02 12:14:19 -0700828
Doug Zongker37974732010-09-16 17:44:38 -0700829def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800830 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700831
Tao Bao9dd909e2017-11-14 11:27:32 -0800832 For non-AVB images, raise exception if the data is too big. Print a warning
833 if the data is nearing the maximum size.
834
835 For AVB images, the actual image size should be identical to the limit.
836
837 Args:
838 data: A string that contains all the data for the partition.
839 target: The partition name. The ".img" suffix is optional.
840 info_dict: The dict to be looked up for relevant info.
841 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700842 if target.endswith(".img"):
843 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700844 mount_point = "/" + target
845
Ying Wangf8824af2014-06-03 14:07:27 -0700846 fs_type = None
847 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700848 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700849 if mount_point == "/userdata":
850 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700851 p = info_dict["fstab"][mount_point]
852 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800853 device = p.device
854 if "/" in device:
855 device = device[device.rfind("/")+1:]
856 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700857 if not fs_type or not limit:
858 return
Doug Zongkereef39442009-04-02 12:14:19 -0700859
Andrew Boie0f9aec82012-02-14 09:32:52 -0800860 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800861 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
862 # path.
863 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
864 if size != limit:
865 raise ExternalError(
866 "Mismatching image size for %s: expected %d actual %d" % (
867 target, limit, size))
868 else:
869 pct = float(size) * 100.0 / limit
870 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
871 if pct >= 99.0:
872 raise ExternalError(msg)
873 elif pct >= 95.0:
874 print("\n WARNING: %s\n" % (msg,))
875 elif OPTIONS.verbose:
876 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700877
878
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800879def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800880 """Parses the APK certs info from a given target-files zip.
881
882 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
883 tuple with the following elements: (1) a dictionary that maps packages to
884 certs (based on the "certificate" and "private_key" attributes in the file;
885 (2) a string representing the extension of compressed APKs in the target files
886 (e.g ".gz", ".bro").
887
888 Args:
889 tf_zip: The input target_files ZipFile (already open).
890
891 Returns:
892 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
893 the extension string of compressed APKs (e.g. ".gz"), or None if there's
894 no compressed APKs.
895 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800896 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100897 compressed_extension = None
898
Tao Bao0f990332017-09-08 19:02:54 -0700899 # META/apkcerts.txt contains the info for _all_ the packages known at build
900 # time. Filter out the ones that are not installed.
901 installed_files = set()
902 for name in tf_zip.namelist():
903 basename = os.path.basename(name)
904 if basename:
905 installed_files.add(basename)
906
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800907 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
908 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700909 if not line:
910 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800911 m = re.match(
912 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
913 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
914 line)
915 if not m:
916 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100917
Tao Bao818ddf52018-01-05 11:17:34 -0800918 matches = m.groupdict()
919 cert = matches["CERT"]
920 privkey = matches["PRIVKEY"]
921 name = matches["NAME"]
922 this_compressed_extension = matches["COMPRESSED"]
923
924 public_key_suffix_len = len(OPTIONS.public_key_suffix)
925 private_key_suffix_len = len(OPTIONS.private_key_suffix)
926 if cert in SPECIAL_CERT_STRINGS and not privkey:
927 certmap[name] = cert
928 elif (cert.endswith(OPTIONS.public_key_suffix) and
929 privkey.endswith(OPTIONS.private_key_suffix) and
930 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
931 certmap[name] = cert[:-public_key_suffix_len]
932 else:
933 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
934
935 if not this_compressed_extension:
936 continue
937
938 # Only count the installed files.
939 filename = name + '.' + this_compressed_extension
940 if filename not in installed_files:
941 continue
942
943 # Make sure that all the values in the compression map have the same
944 # extension. We don't support multiple compression methods in the same
945 # system image.
946 if compressed_extension:
947 if this_compressed_extension != compressed_extension:
948 raise ValueError(
949 "Multiple compressed extensions: {} vs {}".format(
950 compressed_extension, this_compressed_extension))
951 else:
952 compressed_extension = this_compressed_extension
953
954 return (certmap,
955 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800956
957
Doug Zongkereef39442009-04-02 12:14:19 -0700958COMMON_DOCSTRING = """
959 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700960 Prepend <dir>/bin to the list of places to search for binaries
961 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700962
Doug Zongker05d3dea2009-06-22 11:32:31 -0700963 -s (--device_specific) <file>
964 Path to the python module containing device-specific
965 releasetools code.
966
Doug Zongker8bec09e2009-11-30 15:37:14 -0800967 -x (--extra) <key=value>
968 Add a key/value pair to the 'extras' dict, which device-specific
969 extension code may look at.
970
Doug Zongkereef39442009-04-02 12:14:19 -0700971 -v (--verbose)
972 Show command lines being executed.
973
974 -h (--help)
975 Display this usage message and exit.
976"""
977
978def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800979 print(docstring.rstrip("\n"))
980 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700981
982
983def ParseOptions(argv,
984 docstring,
985 extra_opts="", extra_long_opts=(),
986 extra_option_handler=None):
987 """Parse the options in argv and return any arguments that aren't
988 flags. docstring is the calling module's docstring, to be displayed
989 for errors and -h. extra_opts and extra_long_opts are for flags
990 defined by the caller, which are processed by passing them to
991 extra_option_handler."""
992
993 try:
994 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800995 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800996 ["help", "verbose", "path=", "signapk_path=",
997 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700998 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700999 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1000 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001001 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001002 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001003 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001004 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001005 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001006 sys.exit(2)
1007
Doug Zongkereef39442009-04-02 12:14:19 -07001008 for o, a in opts:
1009 if o in ("-h", "--help"):
1010 Usage(docstring)
1011 sys.exit()
1012 elif o in ("-v", "--verbose"):
1013 OPTIONS.verbose = True
1014 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001015 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001016 elif o in ("--signapk_path",):
1017 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001018 elif o in ("--signapk_shared_library_path",):
1019 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001020 elif o in ("--extra_signapk_args",):
1021 OPTIONS.extra_signapk_args = shlex.split(a)
1022 elif o in ("--java_path",):
1023 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001024 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001025 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001026 elif o in ("--public_key_suffix",):
1027 OPTIONS.public_key_suffix = a
1028 elif o in ("--private_key_suffix",):
1029 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001030 elif o in ("--boot_signer_path",):
1031 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001032 elif o in ("--boot_signer_args",):
1033 OPTIONS.boot_signer_args = shlex.split(a)
1034 elif o in ("--verity_signer_path",):
1035 OPTIONS.verity_signer_path = a
1036 elif o in ("--verity_signer_args",):
1037 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001038 elif o in ("-s", "--device_specific"):
1039 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001040 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001041 key, value = a.split("=", 1)
1042 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001043 else:
1044 if extra_option_handler is None or not extra_option_handler(o, a):
1045 assert False, "unknown option \"%s\"" % (o,)
1046
Doug Zongker85448772014-09-09 14:59:20 -07001047 if OPTIONS.search_path:
1048 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1049 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001050
1051 return args
1052
1053
Tao Bao4c851b12016-09-19 13:54:38 -07001054def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001055 """Make a temp file and add it to the list of things to be deleted
1056 when Cleanup() is called. Return the filename."""
1057 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1058 os.close(fd)
1059 OPTIONS.tempfiles.append(fn)
1060 return fn
1061
1062
Tao Bao1c830bf2017-12-25 10:43:47 -08001063def MakeTempDir(prefix='tmp', suffix=''):
1064 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1065
1066 Returns:
1067 The absolute pathname of the new directory.
1068 """
1069 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1070 OPTIONS.tempfiles.append(dir_name)
1071 return dir_name
1072
1073
Doug Zongkereef39442009-04-02 12:14:19 -07001074def Cleanup():
1075 for i in OPTIONS.tempfiles:
1076 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001077 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001078 else:
1079 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001080 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001081
1082
1083class PasswordManager(object):
1084 def __init__(self):
1085 self.editor = os.getenv("EDITOR", None)
1086 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
1087
1088 def GetPasswords(self, items):
1089 """Get passwords corresponding to each string in 'items',
1090 returning a dict. (The dict may have keys in addition to the
1091 values in 'items'.)
1092
1093 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1094 user edit that file to add more needed passwords. If no editor is
1095 available, or $ANDROID_PW_FILE isn't define, prompts the user
1096 interactively in the ordinary way.
1097 """
1098
1099 current = self.ReadFile()
1100
1101 first = True
1102 while True:
1103 missing = []
1104 for i in items:
1105 if i not in current or not current[i]:
1106 missing.append(i)
1107 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001108 if not missing:
1109 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001110
1111 for i in missing:
1112 current[i] = ""
1113
1114 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001115 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001116 answer = raw_input("try to edit again? [y]> ").strip()
1117 if answer and answer[0] not in 'yY':
1118 raise RuntimeError("key passwords unavailable")
1119 first = False
1120
1121 current = self.UpdateAndReadFile(current)
1122
Dan Albert8b72aef2015-03-23 19:13:21 -07001123 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001124 """Prompt the user to enter a value (password) for each key in
1125 'current' whose value is fales. Returns a new dict with all the
1126 values.
1127 """
1128 result = {}
1129 for k, v in sorted(current.iteritems()):
1130 if v:
1131 result[k] = v
1132 else:
1133 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001134 result[k] = getpass.getpass(
1135 "Enter password for %s key> " % k).strip()
1136 if result[k]:
1137 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001138 return result
1139
1140 def UpdateAndReadFile(self, current):
1141 if not self.editor or not self.pwfile:
1142 return self.PromptResult(current)
1143
1144 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001145 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001146 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1147 f.write("# (Additional spaces are harmless.)\n\n")
1148
1149 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001150 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1151 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001152 f.write("[[[ %s ]]] %s\n" % (v, k))
1153 if not v and first_line is None:
1154 # position cursor on first line with no password.
1155 first_line = i + 4
1156 f.close()
1157
1158 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1159 _, _ = p.communicate()
1160
1161 return self.ReadFile()
1162
1163 def ReadFile(self):
1164 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001165 if self.pwfile is None:
1166 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001167 try:
1168 f = open(self.pwfile, "r")
1169 for line in f:
1170 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001171 if not line or line[0] == '#':
1172 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001173 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1174 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001175 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001176 else:
1177 result[m.group(2)] = m.group(1)
1178 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001179 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001180 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001181 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001182 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001183
1184
Dan Albert8e0178d2015-01-27 15:53:15 -08001185def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1186 compress_type=None):
1187 import datetime
1188
1189 # http://b/18015246
1190 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1191 # for files larger than 2GiB. We can work around this by adjusting their
1192 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1193 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1194 # it isn't clear to me exactly what circumstances cause this).
1195 # `zipfile.write()` must be used directly to work around this.
1196 #
1197 # This mess can be avoided if we port to python3.
1198 saved_zip64_limit = zipfile.ZIP64_LIMIT
1199 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1200
1201 if compress_type is None:
1202 compress_type = zip_file.compression
1203 if arcname is None:
1204 arcname = filename
1205
1206 saved_stat = os.stat(filename)
1207
1208 try:
1209 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1210 # file to be zipped and reset it when we're done.
1211 os.chmod(filename, perms)
1212
1213 # Use a fixed timestamp so the output is repeatable.
1214 epoch = datetime.datetime.fromtimestamp(0)
1215 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1216 os.utime(filename, (timestamp, timestamp))
1217
1218 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1219 finally:
1220 os.chmod(filename, saved_stat.st_mode)
1221 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1222 zipfile.ZIP64_LIMIT = saved_zip64_limit
1223
1224
Tao Bao58c1b962015-05-20 09:32:18 -07001225def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001226 compress_type=None):
1227 """Wrap zipfile.writestr() function to work around the zip64 limit.
1228
1229 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1230 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1231 when calling crc32(bytes).
1232
1233 But it still works fine to write a shorter string into a large zip file.
1234 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1235 when we know the string won't be too long.
1236 """
1237
1238 saved_zip64_limit = zipfile.ZIP64_LIMIT
1239 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1240
1241 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1242 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001243 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001244 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001245 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001246 else:
Tao Baof3282b42015-04-01 11:21:55 -07001247 zinfo = zinfo_or_arcname
1248
1249 # If compress_type is given, it overrides the value in zinfo.
1250 if compress_type is not None:
1251 zinfo.compress_type = compress_type
1252
Tao Bao58c1b962015-05-20 09:32:18 -07001253 # If perms is given, it has a priority.
1254 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001255 # If perms doesn't set the file type, mark it as a regular file.
1256 if perms & 0o770000 == 0:
1257 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001258 zinfo.external_attr = perms << 16
1259
Tao Baof3282b42015-04-01 11:21:55 -07001260 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001261 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1262
Dan Albert8b72aef2015-03-23 19:13:21 -07001263 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001264 zipfile.ZIP64_LIMIT = saved_zip64_limit
1265
1266
Tao Bao89d7ab22017-12-14 17:05:33 -08001267def ZipDelete(zip_filename, entries):
1268 """Deletes entries from a ZIP file.
1269
1270 Since deleting entries from a ZIP file is not supported, it shells out to
1271 'zip -d'.
1272
1273 Args:
1274 zip_filename: The name of the ZIP file.
1275 entries: The name of the entry, or the list of names to be deleted.
1276
1277 Raises:
1278 AssertionError: In case of non-zero return from 'zip'.
1279 """
1280 if isinstance(entries, basestring):
1281 entries = [entries]
1282 cmd = ["zip", "-d", zip_filename] + entries
1283 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1284 stdoutdata, _ = proc.communicate()
1285 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1286 stdoutdata)
1287
1288
Tao Baof3282b42015-04-01 11:21:55 -07001289def ZipClose(zip_file):
1290 # http://b/18015246
1291 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1292 # central directory.
1293 saved_zip64_limit = zipfile.ZIP64_LIMIT
1294 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1295
1296 zip_file.close()
1297
1298 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001299
1300
1301class DeviceSpecificParams(object):
1302 module = None
1303 def __init__(self, **kwargs):
1304 """Keyword arguments to the constructor become attributes of this
1305 object, which is passed to all functions in the device-specific
1306 module."""
1307 for k, v in kwargs.iteritems():
1308 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001309 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001310
1311 if self.module is None:
1312 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001313 if not path:
1314 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001315 try:
1316 if os.path.isdir(path):
1317 info = imp.find_module("releasetools", [path])
1318 else:
1319 d, f = os.path.split(path)
1320 b, x = os.path.splitext(f)
1321 if x == ".py":
1322 f = b
1323 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001324 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001325 self.module = imp.load_module("device_specific", *info)
1326 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001327 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001328
1329 def _DoCall(self, function_name, *args, **kwargs):
1330 """Call the named function in the device-specific module, passing
1331 the given args and kwargs. The first argument to the call will be
1332 the DeviceSpecific object itself. If there is no module, or the
1333 module does not define the function, return the value of the
1334 'default' kwarg (which itself defaults to None)."""
1335 if self.module is None or not hasattr(self.module, function_name):
1336 return kwargs.get("default", None)
1337 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1338
1339 def FullOTA_Assertions(self):
1340 """Called after emitting the block of assertions at the top of a
1341 full OTA package. Implementations can add whatever additional
1342 assertions they like."""
1343 return self._DoCall("FullOTA_Assertions")
1344
Doug Zongkere5ff5902012-01-17 10:55:37 -08001345 def FullOTA_InstallBegin(self):
1346 """Called at the start of full OTA installation."""
1347 return self._DoCall("FullOTA_InstallBegin")
1348
Doug Zongker05d3dea2009-06-22 11:32:31 -07001349 def FullOTA_InstallEnd(self):
1350 """Called at the end of full OTA installation; typically this is
1351 used to install the image for the device's baseband processor."""
1352 return self._DoCall("FullOTA_InstallEnd")
1353
1354 def IncrementalOTA_Assertions(self):
1355 """Called after emitting the block of assertions at the top of an
1356 incremental OTA package. Implementations can add whatever
1357 additional assertions they like."""
1358 return self._DoCall("IncrementalOTA_Assertions")
1359
Doug Zongkere5ff5902012-01-17 10:55:37 -08001360 def IncrementalOTA_VerifyBegin(self):
1361 """Called at the start of the verification phase of incremental
1362 OTA installation; additional checks can be placed here to abort
1363 the script before any changes are made."""
1364 return self._DoCall("IncrementalOTA_VerifyBegin")
1365
Doug Zongker05d3dea2009-06-22 11:32:31 -07001366 def IncrementalOTA_VerifyEnd(self):
1367 """Called at the end of the verification phase of incremental OTA
1368 installation; additional checks can be placed here to abort the
1369 script before any changes are made."""
1370 return self._DoCall("IncrementalOTA_VerifyEnd")
1371
Doug Zongkere5ff5902012-01-17 10:55:37 -08001372 def IncrementalOTA_InstallBegin(self):
1373 """Called at the start of incremental OTA installation (after
1374 verification is complete)."""
1375 return self._DoCall("IncrementalOTA_InstallBegin")
1376
Doug Zongker05d3dea2009-06-22 11:32:31 -07001377 def IncrementalOTA_InstallEnd(self):
1378 """Called at the end of incremental OTA installation; typically
1379 this is used to install the image for the device's baseband
1380 processor."""
1381 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001382
Tao Bao9bc6bb22015-11-09 16:58:28 -08001383 def VerifyOTA_Assertions(self):
1384 return self._DoCall("VerifyOTA_Assertions")
1385
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001386class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001387 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001388 self.name = name
1389 self.data = data
1390 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001391 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001392 self.sha1 = sha1(data).hexdigest()
1393
1394 @classmethod
1395 def FromLocalFile(cls, name, diskname):
1396 f = open(diskname, "rb")
1397 data = f.read()
1398 f.close()
1399 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001400
1401 def WriteToTemp(self):
1402 t = tempfile.NamedTemporaryFile()
1403 t.write(self.data)
1404 t.flush()
1405 return t
1406
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001407 def WriteToDir(self, d):
1408 with open(os.path.join(d, self.name), "wb") as fp:
1409 fp.write(self.data)
1410
Geremy Condra36bd3652014-02-06 19:45:10 -08001411 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001412 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001413
1414DIFF_PROGRAM_BY_EXT = {
1415 ".gz" : "imgdiff",
1416 ".zip" : ["imgdiff", "-z"],
1417 ".jar" : ["imgdiff", "-z"],
1418 ".apk" : ["imgdiff", "-z"],
1419 ".img" : "imgdiff",
1420 }
1421
1422class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001423 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001424 self.tf = tf
1425 self.sf = sf
1426 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001427 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001428
1429 def ComputePatch(self):
1430 """Compute the patch (as a string of data) needed to turn sf into
1431 tf. Returns the same tuple as GetPatch()."""
1432
1433 tf = self.tf
1434 sf = self.sf
1435
Doug Zongker24cd2802012-08-14 16:36:15 -07001436 if self.diff_program:
1437 diff_program = self.diff_program
1438 else:
1439 ext = os.path.splitext(tf.name)[1]
1440 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001441
1442 ttemp = tf.WriteToTemp()
1443 stemp = sf.WriteToTemp()
1444
1445 ext = os.path.splitext(tf.name)[1]
1446
1447 try:
1448 ptemp = tempfile.NamedTemporaryFile()
1449 if isinstance(diff_program, list):
1450 cmd = copy.copy(diff_program)
1451 else:
1452 cmd = [diff_program]
1453 cmd.append(stemp.name)
1454 cmd.append(ttemp.name)
1455 cmd.append(ptemp.name)
1456 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001457 err = []
1458 def run():
1459 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001460 if e:
1461 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001462 th = threading.Thread(target=run)
1463 th.start()
1464 th.join(timeout=300) # 5 mins
1465 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001466 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001467 p.terminate()
1468 th.join(5)
1469 if th.is_alive():
1470 p.kill()
1471 th.join()
1472
Tianjie Xua2a9f992018-01-05 15:15:54 -08001473 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001474 print("WARNING: failure running %s:\n%s\n" % (
1475 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001476 self.patch = None
1477 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001478 diff = ptemp.read()
1479 finally:
1480 ptemp.close()
1481 stemp.close()
1482 ttemp.close()
1483
1484 self.patch = diff
1485 return self.tf, self.sf, self.patch
1486
1487
1488 def GetPatch(self):
1489 """Return a tuple (target_file, source_file, patch_data).
1490 patch_data may be None if ComputePatch hasn't been called, or if
1491 computing the patch failed."""
1492 return self.tf, self.sf, self.patch
1493
1494
1495def ComputeDifferences(diffs):
1496 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001497 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001498
1499 # Do the largest files first, to try and reduce the long-pole effect.
1500 by_size = [(i.tf.size, i) for i in diffs]
1501 by_size.sort(reverse=True)
1502 by_size = [i[1] for i in by_size]
1503
1504 lock = threading.Lock()
1505 diff_iter = iter(by_size) # accessed under lock
1506
1507 def worker():
1508 try:
1509 lock.acquire()
1510 for d in diff_iter:
1511 lock.release()
1512 start = time.time()
1513 d.ComputePatch()
1514 dur = time.time() - start
1515 lock.acquire()
1516
1517 tf, sf, patch = d.GetPatch()
1518 if sf.name == tf.name:
1519 name = tf.name
1520 else:
1521 name = "%s (%s)" % (tf.name, sf.name)
1522 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001523 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001524 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001525 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1526 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001527 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001528 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001529 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001530 raise
1531
1532 # start worker threads; wait for them all to finish.
1533 threads = [threading.Thread(target=worker)
1534 for i in range(OPTIONS.worker_threads)]
1535 for th in threads:
1536 th.start()
1537 while threads:
1538 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001539
1540
Dan Albert8b72aef2015-03-23 19:13:21 -07001541class BlockDifference(object):
1542 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001543 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001544 self.tgt = tgt
1545 self.src = src
1546 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001547 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001548 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001549
Tao Baodd2a5892015-03-12 12:32:37 -07001550 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001551 version = max(
1552 int(i) for i in
1553 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001554 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001555 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001556
1557 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001558 version=self.version,
1559 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001560 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001561 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001562 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001563 self.touched_src_ranges = b.touched_src_ranges
1564 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001565
Tao Baoaac4ad52015-10-16 15:26:34 -07001566 if src is None:
1567 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1568 else:
1569 _, self.device = GetTypeAndDevice("/" + partition,
1570 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001571
Tao Baod8d14be2016-02-04 14:26:02 -08001572 @property
1573 def required_cache(self):
1574 return self._required_cache
1575
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001576 def WriteScript(self, script, output_zip, progress=None):
1577 if not self.src:
1578 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001579 script.Print("Patching %s image unconditionally..." % (self.partition,))
1580 else:
1581 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001582
Dan Albert8b72aef2015-03-23 19:13:21 -07001583 if progress:
1584 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001585 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001586 if OPTIONS.verify:
1587 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001588
Tao Bao9bc6bb22015-11-09 16:58:28 -08001589 def WriteStrictVerifyScript(self, script):
1590 """Verify all the blocks in the care_map, including clobbered blocks.
1591
1592 This differs from the WriteVerifyScript() function: a) it prints different
1593 error messages; b) it doesn't allow half-way updated images to pass the
1594 verification."""
1595
1596 partition = self.partition
1597 script.Print("Verifying %s..." % (partition,))
1598 ranges = self.tgt.care_map
1599 ranges_str = ranges.to_string_raw()
1600 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1601 'ui_print(" Verified.") || '
1602 'ui_print("\\"%s\\" has unexpected contents.");' % (
1603 self.device, ranges_str,
1604 self.tgt.TotalSha1(include_clobbered_blocks=True),
1605 self.device))
1606 script.AppendExtra("")
1607
Tao Baod522bdc2016-04-12 15:53:16 -07001608 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001609 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001610
1611 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001612 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001613 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001614
1615 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001616 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001617 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001618 ranges = self.touched_src_ranges
1619 expected_sha1 = self.touched_src_sha1
1620 else:
1621 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1622 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001623
1624 # No blocks to be checked, skipping.
1625 if not ranges:
1626 return
1627
Tao Bao5ece99d2015-05-12 11:42:31 -07001628 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001629 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1630 'block_image_verify("%s", '
1631 'package_extract_file("%s.transfer.list"), '
1632 '"%s.new.dat", "%s.patch.dat")) then') % (
1633 self.device, ranges_str, expected_sha1,
1634 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001635 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001636 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001637
Tianjie Xufc3422a2015-12-15 11:53:59 -08001638 if self.version >= 4:
1639
1640 # Bug: 21124327
1641 # When generating incrementals for the system and vendor partitions in
1642 # version 4 or newer, explicitly check the first block (which contains
1643 # the superblock) of the partition to see if it's what we expect. If
1644 # this check fails, give an explicit log message about the partition
1645 # having been remounted R/W (the most likely explanation).
1646 if self.check_first_block:
1647 script.AppendExtra('check_first_block("%s");' % (self.device,))
1648
1649 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001650 if partition == "system":
1651 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1652 else:
1653 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001654 script.AppendExtra((
1655 'ifelse (block_image_recover("{device}", "{ranges}") && '
1656 'block_image_verify("{device}", '
1657 'package_extract_file("{partition}.transfer.list"), '
1658 '"{partition}.new.dat", "{partition}.patch.dat"), '
1659 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001660 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001661 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001662 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001663
Tao Baodd2a5892015-03-12 12:32:37 -07001664 # Abort the OTA update. Note that the incremental OTA cannot be applied
1665 # even if it may match the checksum of the target partition.
1666 # a) If version < 3, operations like move and erase will make changes
1667 # unconditionally and damage the partition.
1668 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001669 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001670 if partition == "system":
1671 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1672 else:
1673 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1674 script.AppendExtra((
1675 'abort("E%d: %s partition has unexpected contents");\n'
1676 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001677
Tao Bao5fcaaef2015-06-01 13:40:49 -07001678 def _WritePostInstallVerifyScript(self, script):
1679 partition = self.partition
1680 script.Print('Verifying the updated %s image...' % (partition,))
1681 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1682 ranges = self.tgt.care_map
1683 ranges_str = ranges.to_string_raw()
1684 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1685 self.device, ranges_str,
1686 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001687
1688 # Bug: 20881595
1689 # Verify that extended blocks are really zeroed out.
1690 if self.tgt.extended:
1691 ranges_str = self.tgt.extended.to_string_raw()
1692 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1693 self.device, ranges_str,
1694 self._HashZeroBlocks(self.tgt.extended.size())))
1695 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001696 if partition == "system":
1697 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1698 else:
1699 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001700 script.AppendExtra(
1701 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001702 ' abort("E%d: %s partition has unexpected non-zero contents after '
1703 'OTA update");\n'
1704 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001705 else:
1706 script.Print('Verified the updated %s image.' % (partition,))
1707
Tianjie Xu209db462016-05-24 17:34:52 -07001708 if partition == "system":
1709 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1710 else:
1711 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1712
Tao Bao5fcaaef2015-06-01 13:40:49 -07001713 script.AppendExtra(
1714 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001715 ' abort("E%d: %s partition has unexpected contents after OTA '
1716 'update");\n'
1717 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001718
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001719 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001720 ZipWrite(output_zip,
1721 '{}.transfer.list'.format(self.path),
1722 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001723
1724 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1725 # almost triples the compression time but doesn't further reduce the size too much.
1726 # For a typical 1.8G system.new.dat
1727 # zip | brotli(quality 6) | brotli(quality 9)
1728 # compressed_size: 942M | 869M (~8% reduced) | 854M
1729 # compression_time: 75s | 265s | 719s
1730 # decompression_time: 15s | 25s | 25s
1731
1732 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001733 brotli_cmd = ['brotli', '--quality=6',
1734 '--output={}.new.dat.br'.format(self.path),
1735 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001736 print("Compressing {}.new.dat with brotli".format(self.partition))
Alex Deymob10e07a2017-11-09 23:53:42 +01001737 p = Run(brotli_cmd, stdout=subprocess.PIPE)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001738 p.communicate()
1739 assert p.returncode == 0,\
1740 'compression of {}.new.dat failed'.format(self.partition)
1741
1742 new_data_name = '{}.new.dat.br'.format(self.partition)
1743 ZipWrite(output_zip,
1744 '{}.new.dat.br'.format(self.path),
1745 new_data_name,
1746 compress_type=zipfile.ZIP_STORED)
1747 else:
1748 new_data_name = '{}.new.dat'.format(self.partition)
1749 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1750
Dan Albert8e0178d2015-01-27 15:53:15 -08001751 ZipWrite(output_zip,
1752 '{}.patch.dat'.format(self.path),
1753 '{}.patch.dat'.format(self.partition),
1754 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001755
Tianjie Xu209db462016-05-24 17:34:52 -07001756 if self.partition == "system":
1757 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1758 else:
1759 code = ErrorCode.VENDOR_UPDATE_FAILURE
1760
Dan Albert8e0178d2015-01-27 15:53:15 -08001761 call = ('block_image_update("{device}", '
1762 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001763 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001764 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001765 device=self.device, partition=self.partition,
1766 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001767 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001768
Dan Albert8b72aef2015-03-23 19:13:21 -07001769 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001770 data = source.ReadRangeSet(ranges)
1771 ctx = sha1()
1772
1773 for p in data:
1774 ctx.update(p)
1775
1776 return ctx.hexdigest()
1777
Tao Baoe9b61912015-07-09 17:37:49 -07001778 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1779 """Return the hash value for all zero blocks."""
1780 zero_block = '\x00' * 4096
1781 ctx = sha1()
1782 for _ in range(num_blocks):
1783 ctx.update(zero_block)
1784
1785 return ctx.hexdigest()
1786
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001787
1788DataImage = blockimgdiff.DataImage
1789
Doug Zongker96a57e72010-09-26 14:57:41 -07001790# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001791PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001792 "ext4": "EMMC",
1793 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001794 "f2fs": "EMMC",
1795 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001796}
Doug Zongker96a57e72010-09-26 14:57:41 -07001797
1798def GetTypeAndDevice(mount_point, info):
1799 fstab = info["fstab"]
1800 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001801 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1802 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001803 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001804 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001805
1806
1807def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001808 """Parses and converts a PEM-encoded certificate into DER-encoded.
1809
1810 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1811
1812 Returns:
1813 The decoded certificate string.
1814 """
1815 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001816 save = False
1817 for line in data.split("\n"):
1818 if "--END CERTIFICATE--" in line:
1819 break
1820 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001821 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001822 if "--BEGIN CERTIFICATE--" in line:
1823 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001824 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001825 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001826
Tao Bao04e1f012018-02-04 12:13:35 -08001827
1828def ExtractPublicKey(cert):
1829 """Extracts the public key (PEM-encoded) from the given certificate file.
1830
1831 Args:
1832 cert: The certificate filename.
1833
1834 Returns:
1835 The public key string.
1836
1837 Raises:
1838 AssertionError: On non-zero return from 'openssl'.
1839 """
1840 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1841 # While openssl 1.1 writes the key into the given filename followed by '-out',
1842 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1843 # stdout instead.
1844 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1845 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1846 pubkey, stderrdata = proc.communicate()
1847 assert proc.returncode == 0, \
1848 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1849 return pubkey
1850
1851
Doug Zongker412c02f2014-02-13 10:58:24 -08001852def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1853 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001854 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001855
Tao Bao6d5d6232018-03-09 17:04:42 -08001856 Most of the space in the boot and recovery images is just the kernel, which is
1857 identical for the two, so the resulting patch should be efficient. Add it to
1858 the output zip, along with a shell script that is run from init.rc on first
1859 boot to actually do the patching and install the new recovery image.
1860
1861 Args:
1862 input_dir: The top-level input directory of the target-files.zip.
1863 output_sink: The callback function that writes the result.
1864 recovery_img: File object for the recovery image.
1865 boot_img: File objects for the boot image.
1866 info_dict: A dict returned by common.LoadInfoDict() on the input
1867 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08001868 """
Doug Zongker412c02f2014-02-13 10:58:24 -08001869 if info_dict is None:
1870 info_dict = OPTIONS.info_dict
1871
Tao Bao6d5d6232018-03-09 17:04:42 -08001872 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001873
Tao Baof2cffbd2015-07-22 12:33:18 -07001874 if full_recovery_image:
1875 output_sink("etc/recovery.img", recovery_img.data)
1876
1877 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08001878 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07001879 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08001880 # With system-root-image, boot and recovery images will have mismatching
1881 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
1882 # to handle such a case.
1883 if system_root_image:
1884 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07001885 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08001886 assert not os.path.exists(path)
1887 else:
1888 diff_program = ["imgdiff"]
1889 if os.path.exists(path):
1890 diff_program.append("-b")
1891 diff_program.append(path)
1892 bonus_args = "-b /system/etc/recovery-resource.dat"
1893 else:
1894 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07001895
1896 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1897 _, _, patch = d.ComputePatch()
1898 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001899
Dan Albertebb19aa2015-03-27 19:11:53 -07001900 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001901 # The following GetTypeAndDevice()s need to use the path in the target
1902 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001903 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1904 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1905 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001906 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001907
Tao Baof2cffbd2015-07-22 12:33:18 -07001908 if full_recovery_image:
1909 sh = """#!/system/bin/sh
1910if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1911 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"
1912else
1913 log -t recovery "Recovery image already installed"
1914fi
1915""" % {'type': recovery_type,
1916 'device': recovery_device,
1917 'sha1': recovery_img.sha1,
1918 'size': recovery_img.size}
1919 else:
1920 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001921if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1922 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"
1923else
1924 log -t recovery "Recovery image already installed"
1925fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001926""" % {'boot_size': boot_img.size,
1927 'boot_sha1': boot_img.sha1,
1928 'recovery_size': recovery_img.size,
1929 'recovery_sha1': recovery_img.sha1,
1930 'boot_type': boot_type,
1931 'boot_device': boot_device,
1932 'recovery_type': recovery_type,
1933 'recovery_device': recovery_device,
1934 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001935
1936 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001937 # in the L release.
1938 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001939
Tao Bao89fbb0f2017-01-10 10:47:58 -08001940 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001941
1942 output_sink(sh_location, sh)