blob: 75c86cc22cda89939a634c192d557daa3984d709 [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
Doug Zongker05d3dea2009-06-22 11:32:31 -070021import imp
Doug Zongkereef39442009-04-02 12:14:19 -070022import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080023import platform
Doug Zongkereef39442009-04-02 12:14:19 -070024import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070025import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070026import shutil
27import subprocess
28import sys
29import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070030import threading
31import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070032import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070033
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034import blockimgdiff
35
Tao Baof3282b42015-04-01 11:21:55 -070036from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080037
Doug Zongkereef39442009-04-02 12:14:19 -070038
Dan Albert8b72aef2015-03-23 19:13:21 -070039class Options(object):
40 def __init__(self):
41 platform_search_path = {
42 "linux2": "out/host/linux-x86",
43 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070044 }
Doug Zongker85448772014-09-09 14:59:20 -070045
Dan Albert8b72aef2015-03-23 19:13:21 -070046 self.search_path = platform_search_path.get(sys.platform, None)
47 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080048 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070049 self.extra_signapk_args = []
50 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080051 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070052 self.public_key_suffix = ".x509.pem"
53 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070054 # use otatools built boot_signer by default
55 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070056 self.boot_signer_args = []
57 self.verity_signer_path = None
58 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070059 self.verbose = False
60 self.tempfiles = []
61 self.device_specific = None
62 self.extras = {}
63 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070064 self.source_info_dict = None
65 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070066 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070067 # Stash size cannot exceed cache_size * threshold.
68 self.cache_size = None
69 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070070
71
72OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070073
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080074
75# Values for "certificate" in apkcerts that mean special things.
76SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
77
Tao Bao9dd909e2017-11-14 11:27:32 -080078
79# The partitions allowed to be signed by AVB (Android verified boot 2.0).
80AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'dtbo')
81
82
Tianjie Xu209db462016-05-24 17:34:52 -070083class ErrorCode(object):
84 """Define error_codes for failures that happen during the actual
85 update package installation.
86
87 Error codes 0-999 are reserved for failures before the package
88 installation (i.e. low battery, package verification failure).
89 Detailed code in 'bootable/recovery/error_code.h' """
90
91 SYSTEM_VERIFICATION_FAILURE = 1000
92 SYSTEM_UPDATE_FAILURE = 1001
93 SYSTEM_UNEXPECTED_CONTENTS = 1002
94 SYSTEM_NONZERO_CONTENTS = 1003
95 SYSTEM_RECOVER_FAILURE = 1004
96 VENDOR_VERIFICATION_FAILURE = 2000
97 VENDOR_UPDATE_FAILURE = 2001
98 VENDOR_UNEXPECTED_CONTENTS = 2002
99 VENDOR_NONZERO_CONTENTS = 2003
100 VENDOR_RECOVER_FAILURE = 2004
101 OEM_PROP_MISMATCH = 3000
102 FINGERPRINT_MISMATCH = 3001
103 THUMBPRINT_MISMATCH = 3002
104 OLDER_BUILD = 3003
105 DEVICE_MISMATCH = 3004
106 BAD_PATCH_FILE = 3005
107 INSUFFICIENT_CACHE_SPACE = 3006
108 TUNE_PARTITION_FAILURE = 3007
109 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800110
Dan Albert8b72aef2015-03-23 19:13:21 -0700111class ExternalError(RuntimeError):
112 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700113
114
Tao Bao39451582017-05-04 11:10:47 -0700115def Run(args, verbose=None, **kwargs):
116 """Create and return a subprocess.Popen object.
117
118 Caller can specify if the command line should be printed. The global
119 OPTIONS.verbose will be used if not specified.
120 """
121 if verbose is None:
122 verbose = OPTIONS.verbose
123 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800124 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700125 return subprocess.Popen(args, **kwargs)
126
127
Ying Wang7e6d4e42010-12-13 16:25:36 -0800128def CloseInheritedPipes():
129 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
130 before doing other work."""
131 if platform.system() != "Darwin":
132 return
133 for d in range(3, 1025):
134 try:
135 stat = os.fstat(d)
136 if stat is not None:
137 pipebit = stat[0] & 0x1000
138 if pipebit != 0:
139 os.close(d)
140 except OSError:
141 pass
142
143
Tao Bao2c15d9e2015-07-09 11:51:16 -0700144def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700145 """Read and parse the META/misc_info.txt key/value pairs from the
146 input target files and return a dict."""
147
Doug Zongkerc9253822014-02-04 12:17:58 -0800148 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700149 if isinstance(input_file, zipfile.ZipFile):
150 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800151 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700152 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800153 try:
154 with open(path) as f:
155 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700156 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800157 if e.errno == errno.ENOENT:
158 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800159
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700160 try:
Michael Runge6e836112014-04-15 17:40:21 -0700161 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700162 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800163 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700164
Tao Bao6cd54732017-02-27 15:12:05 -0800165 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800166 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800167
Tao Bao84e75682015-07-19 02:38:53 -0700168 # A few properties are stored as links to the files in the out/ directory.
169 # It works fine with the build system. However, they are no longer available
170 # when (re)generating from target_files zip. If input_dir is not None, we
171 # are doing repacking. Redirect those properties to the actual files in the
172 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700173 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400174 # We carry a copy of file_contexts.bin under META/. If not available,
175 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700176 # to build images than the one running on device, such as when enabling
177 # system_root_image. In that case, we must have the one for image
178 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700179 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
180 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700181 if d.get("system_root_image") == "true":
182 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700183 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700184 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700185 if not os.path.exists(fc_config):
186 fc_config = None
187
188 if fc_config:
189 d["selinux_fc"] = fc_config
190
Tao Bao84e75682015-07-19 02:38:53 -0700191 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
192 if d.get("system_root_image") == "true":
193 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
194 d["ramdisk_fs_config"] = os.path.join(
195 input_dir, "META", "root_filesystem_config.txt")
196
Tao Baof54216f2016-03-29 15:12:37 -0700197 # Redirect {system,vendor}_base_fs_file.
198 if "system_base_fs_file" in d:
199 basename = os.path.basename(d["system_base_fs_file"])
200 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700201 if os.path.exists(system_base_fs_file):
202 d["system_base_fs_file"] = system_base_fs_file
203 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800204 print("Warning: failed to find system base fs file: %s" % (
205 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700206 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700207
208 if "vendor_base_fs_file" in d:
209 basename = os.path.basename(d["vendor_base_fs_file"])
210 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700211 if os.path.exists(vendor_base_fs_file):
212 d["vendor_base_fs_file"] = vendor_base_fs_file
213 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800214 print("Warning: failed to find vendor base fs file: %s" % (
215 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700216 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700217
Doug Zongker37974732010-09-16 17:44:38 -0700218 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800219 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700220 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700221 if not line:
222 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700223 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700224 if not value:
225 continue
Doug Zongker37974732010-09-16 17:44:38 -0700226 if name == "blocksize":
227 d[name] = value
228 else:
229 d[name + "_size"] = value
230 except KeyError:
231 pass
232
233 def makeint(key):
234 if key in d:
235 d[key] = int(d[key], 0)
236
237 makeint("recovery_api_version")
238 makeint("blocksize")
239 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700240 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700241 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700242 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700243 makeint("recovery_size")
244 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800245 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700246
Tianjie Xucfa86222016-03-07 16:31:19 -0800247 system_root_image = d.get("system_root_image", None) == "true"
248 if d.get("no_recovery", None) != "true":
249 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800250 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800251 recovery_fstab_path, system_root_image)
252 elif d.get("recovery_as_boot", None) == "true":
253 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
254 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
255 recovery_fstab_path, system_root_image)
256 else:
257 d["fstab"] = None
258
Tao Baobcd1d162017-08-26 13:10:26 -0700259 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
260 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700261 return d
262
Tao Baod1de6f32017-03-01 16:38:48 -0800263
Tao Baobcd1d162017-08-26 13:10:26 -0700264def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700265 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700266 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700267 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700268 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700269 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700270 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700271
Tao Baod1de6f32017-03-01 16:38:48 -0800272
Michael Runge6e836112014-04-15 17:40:21 -0700273def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700274 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700275 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700276 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700277 if not line or line.startswith("#"):
278 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700279 if "=" in line:
280 name, value = line.split("=", 1)
281 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700282 return d
283
Tao Baod1de6f32017-03-01 16:38:48 -0800284
Tianjie Xucfa86222016-03-07 16:31:19 -0800285def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
286 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700287 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800288 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700289 self.mount_point = mount_point
290 self.fs_type = fs_type
291 self.device = device
292 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700293 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700294
295 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800296 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700297 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800298 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700299 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700300
Tao Baod1de6f32017-03-01 16:38:48 -0800301 assert fstab_version == 2
302
303 d = {}
304 for line in data.split("\n"):
305 line = line.strip()
306 if not line or line.startswith("#"):
307 continue
308
309 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
310 pieces = line.split()
311 if len(pieces) != 5:
312 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
313
314 # Ignore entries that are managed by vold.
315 options = pieces[4]
316 if "voldmanaged=" in options:
317 continue
318
319 # It's a good line, parse it.
320 length = 0
321 options = options.split(",")
322 for i in options:
323 if i.startswith("length="):
324 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800325 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800326 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700327 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800328
Tao Baod1de6f32017-03-01 16:38:48 -0800329 mount_flags = pieces[3]
330 # Honor the SELinux context if present.
331 context = None
332 for i in mount_flags.split(","):
333 if i.startswith("context="):
334 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800335
Tao Baod1de6f32017-03-01 16:38:48 -0800336 mount_point = pieces[1]
337 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
338 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800339
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700340 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700341 # system. Other areas assume system is always at "/system" so point /system
342 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700343 if system_root_image:
344 assert not d.has_key("/system") and d.has_key("/")
345 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700346 return d
347
348
Doug Zongker37974732010-09-16 17:44:38 -0700349def DumpInfoDict(d):
350 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800351 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700352
Dan Albert8b72aef2015-03-23 19:13:21 -0700353
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800354def AppendAVBSigningArgs(cmd, partition):
355 """Append signing arguments for avbtool."""
356 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
357 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
358 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
359 if key_path and algorithm:
360 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700361 avb_salt = OPTIONS.info_dict.get("avb_salt")
362 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
363 if avb_salt and partition != "vbmeta":
364 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800365
366
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700367def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800368 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700369 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700370
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700371 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800372 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
373 we are building a two-step special image (i.e. building a recovery image to
374 be loaded into /boot in two-step OTAs).
375
376 Return the image data, or None if sourcedir does not appear to contains files
377 for building the requested image.
378 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700379
380 def make_ramdisk():
381 ramdisk_img = tempfile.NamedTemporaryFile()
382
383 if os.access(fs_config_file, os.F_OK):
384 cmd = ["mkbootfs", "-f", fs_config_file,
385 os.path.join(sourcedir, "RAMDISK")]
386 else:
387 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
388 p1 = Run(cmd, stdout=subprocess.PIPE)
389 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
390
391 p2.wait()
392 p1.wait()
393 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
394 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
395
396 return ramdisk_img
397
398 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
399 return None
400
401 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700402 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700403
Doug Zongkerd5131602012-08-02 14:46:42 -0700404 if info_dict is None:
405 info_dict = OPTIONS.info_dict
406
Doug Zongkereef39442009-04-02 12:14:19 -0700407 img = tempfile.NamedTemporaryFile()
408
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700409 if has_ramdisk:
410 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700411
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800412 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
413 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
414
415 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700416
Benoit Fradina45a8682014-07-14 21:00:43 +0200417 fn = os.path.join(sourcedir, "second")
418 if os.access(fn, os.F_OK):
419 cmd.append("--second")
420 cmd.append(fn)
421
Doug Zongker171f1cd2009-06-15 22:36:37 -0700422 fn = os.path.join(sourcedir, "cmdline")
423 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700424 cmd.append("--cmdline")
425 cmd.append(open(fn).read().rstrip("\n"))
426
427 fn = os.path.join(sourcedir, "base")
428 if os.access(fn, os.F_OK):
429 cmd.append("--base")
430 cmd.append(open(fn).read().rstrip("\n"))
431
Ying Wang4de6b5b2010-08-25 14:29:34 -0700432 fn = os.path.join(sourcedir, "pagesize")
433 if os.access(fn, os.F_OK):
434 cmd.append("--pagesize")
435 cmd.append(open(fn).read().rstrip("\n"))
436
Doug Zongkerd5131602012-08-02 14:46:42 -0700437 args = info_dict.get("mkbootimg_args", None)
438 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700439 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700440
Sami Tolvanen3303d902016-03-15 16:49:30 +0000441 args = info_dict.get("mkbootimg_version_args", None)
442 if args and args.strip():
443 cmd.extend(shlex.split(args))
444
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700445 if has_ramdisk:
446 cmd.extend(["--ramdisk", ramdisk_img.name])
447
Tao Baod95e9fd2015-03-29 23:07:41 -0700448 img_unsigned = None
449 if info_dict.get("vboot", None):
450 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700451 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700452 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700453 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700454
Tao Baobf70c312017-07-11 17:27:55 -0700455 # "boot" or "recovery", without extension.
456 partition_name = os.path.basename(sourcedir).lower()
457
Doug Zongker38a649f2009-06-17 09:07:09 -0700458 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700459 p.communicate()
Tao Baobf70c312017-07-11 17:27:55 -0700460 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700461
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100462 if (info_dict.get("boot_signer", None) == "true" and
463 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800464 # Hard-code the path as "/boot" for two-step special recovery image (which
465 # will be loaded into /boot during the two-step OTA).
466 if two_step_image:
467 path = "/boot"
468 else:
Tao Baobf70c312017-07-11 17:27:55 -0700469 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700470 cmd = [OPTIONS.boot_signer_path]
471 cmd.extend(OPTIONS.boot_signer_args)
472 cmd.extend([path, img.name,
473 info_dict["verity_key"] + ".pk8",
474 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700475 p = Run(cmd, stdout=subprocess.PIPE)
476 p.communicate()
477 assert p.returncode == 0, "boot_signer of %s image failed" % path
478
Tao Baod95e9fd2015-03-29 23:07:41 -0700479 # Sign the image if vboot is non-empty.
480 elif info_dict.get("vboot", None):
Tao Baobf70c312017-07-11 17:27:55 -0700481 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700482 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800483 # We have switched from the prebuilt futility binary to using the tool
484 # (futility-host) built from the source. Override the setting in the old
485 # TF.zip.
486 futility = info_dict["futility"]
487 if futility.startswith("prebuilts/"):
488 futility = "futility-host"
489 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700490 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700491 info_dict["vboot_key"] + ".vbprivk",
492 info_dict["vboot_subkey"] + ".vbprivk",
493 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700494 img.name]
495 p = Run(cmd, stdout=subprocess.PIPE)
496 p.communicate()
497 assert p.returncode == 0, "vboot_signer of %s image failed" % path
498
Tao Baof3282b42015-04-01 11:21:55 -0700499 # Clean up the temp files.
500 img_unsigned.close()
501 img_keyblock.close()
502
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400503 # AVB: if enabled, calculate and add hash to boot.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800504 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700505 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
506 part_size = info_dict["boot_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400507 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700508 "--partition_size", str(part_size), "--partition_name",
509 partition_name]
510 AppendAVBSigningArgs(cmd, partition_name)
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800511 args = info_dict.get("avb_boot_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400512 if args and args.strip():
513 cmd.extend(shlex.split(args))
514 p = Run(cmd, stdout=subprocess.PIPE)
515 p.communicate()
516 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c312017-07-11 17:27:55 -0700517 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500518
519 img.seek(os.SEEK_SET, 0)
520 data = img.read()
521
522 if has_ramdisk:
523 ramdisk_img.close()
524 img.close()
525
526 return data
527
528
Doug Zongkerd5131602012-08-02 14:46:42 -0700529def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800530 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700531 """Return a File object with the desired bootable image.
532
533 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
534 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
535 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700536
Doug Zongker55d93282011-01-25 17:03:34 -0800537 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
538 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800539 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800540 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700541
542 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
543 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800544 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700545 return File.FromLocalFile(name, prebuilt_path)
546
Tao Bao89fbb0f2017-01-10 10:47:58 -0800547 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700548
549 if info_dict is None:
550 info_dict = OPTIONS.info_dict
551
552 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800553 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
554 # for recovery.
555 has_ramdisk = (info_dict.get("system_root_image") != "true" or
556 prebuilt_name != "boot.img" or
557 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700558
Doug Zongker6f1d0312014-08-22 08:07:12 -0700559 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400560 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
561 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800562 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700563 if data:
564 return File(name, data)
565 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800566
Doug Zongkereef39442009-04-02 12:14:19 -0700567
Doug Zongker75f17362009-12-08 13:46:44 -0800568def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800569 """Unzip the given archive into a temporary directory and return the name.
570
571 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
572 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
573
574 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
575 main file), open for reading.
576 """
Doug Zongkereef39442009-04-02 12:14:19 -0700577
578 tmp = tempfile.mkdtemp(prefix="targetfiles-")
579 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800580
581 def unzip_to_dir(filename, dirname):
582 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
583 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800584 cmd.extend(pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800585 p = Run(cmd, stdout=subprocess.PIPE)
586 p.communicate()
587 if p.returncode != 0:
588 raise ExternalError("failed to unzip input target-files \"%s\"" %
589 (filename,))
590
591 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
592 if m:
593 unzip_to_dir(m.group(1), tmp)
594 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
595 filename = m.group(1)
596 else:
597 unzip_to_dir(filename, tmp)
598
599 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700600
601
602def GetKeyPasswords(keylist):
603 """Given a list of keys, prompt the user to enter passwords for
604 those which require them. Return a {key: password} dict. password
605 will be None if the key has no password."""
606
Doug Zongker8ce7c252009-05-22 13:34:54 -0700607 no_passwords = []
608 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700609 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700610 devnull = open("/dev/null", "w+b")
611 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800612 # We don't need a password for things that aren't really keys.
613 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700614 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700615 continue
616
T.R. Fullhart37e10522013-03-18 10:31:26 -0700617 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700618 "-inform", "DER", "-nocrypt"],
619 stdin=devnull.fileno(),
620 stdout=devnull.fileno(),
621 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700622 p.communicate()
623 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700624 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700625 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700626 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700627 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
628 "-inform", "DER", "-passin", "pass:"],
629 stdin=devnull.fileno(),
630 stdout=devnull.fileno(),
631 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700632 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700633 if p.returncode == 0:
634 # Encrypted key with empty string as password.
635 key_passwords[k] = ''
636 elif stderr.startswith('Error decrypting key'):
637 # Definitely encrypted key.
638 # It would have said "Error reading key" if it didn't parse correctly.
639 need_passwords.append(k)
640 else:
641 # Potentially, a type of key that openssl doesn't understand.
642 # We'll let the routines in signapk.jar handle it.
643 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700644 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700645
T.R. Fullhart37e10522013-03-18 10:31:26 -0700646 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700647 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700648 return key_passwords
649
650
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800651def GetMinSdkVersion(apk_name):
652 """Get the minSdkVersion delared in the APK. This can be both a decimal number
653 (API Level) or a codename.
654 """
655
656 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
657 output, err = p.communicate()
658 if err:
659 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
660 % (p.returncode,))
661
662 for line in output.split("\n"):
663 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
664 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
665 if m:
666 return m.group(1)
667 raise ExternalError("No minSdkVersion returned by aapt")
668
669
670def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
671 """Get the minSdkVersion declared in the APK as a number (API Level). If
672 minSdkVersion is set to a codename, it is translated to a number using the
673 provided map.
674 """
675
676 version = GetMinSdkVersion(apk_name)
677 try:
678 return int(version)
679 except ValueError:
680 # Not a decimal number. Codename?
681 if version in codename_to_api_level_map:
682 return codename_to_api_level_map[version]
683 else:
684 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
685 % (version, codename_to_api_level_map))
686
687
688def SignFile(input_name, output_name, key, password, min_api_level=None,
689 codename_to_api_level_map=dict(),
690 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700691 """Sign the input_name zip/jar/apk, producing output_name. Use the
692 given key and password (the latter may be None if the key does not
693 have a password.
694
Doug Zongker951495f2009-08-14 12:44:19 -0700695 If whole_file is true, use the "-w" option to SignApk to embed a
696 signature that covers the whole file in the archive comment of the
697 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800698
699 min_api_level is the API Level (int) of the oldest platform this file may end
700 up on. If not specified for an APK, the API Level is obtained by interpreting
701 the minSdkVersion attribute of the APK's AndroidManifest.xml.
702
703 codename_to_api_level_map is needed to translate the codename which may be
704 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700705 """
Doug Zongker951495f2009-08-14 12:44:19 -0700706
Alex Klyubin9667b182015-12-10 13:38:50 -0800707 java_library_path = os.path.join(
708 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
709
Tao Baoe95540e2016-11-08 12:08:53 -0800710 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
711 ["-Djava.library.path=" + java_library_path,
712 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
713 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700714 if whole_file:
715 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800716
717 min_sdk_version = min_api_level
718 if min_sdk_version is None:
719 if not whole_file:
720 min_sdk_version = GetMinSdkVersionInt(
721 input_name, codename_to_api_level_map)
722 if min_sdk_version is not None:
723 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
724
T.R. Fullhart37e10522013-03-18 10:31:26 -0700725 cmd.extend([key + OPTIONS.public_key_suffix,
726 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800727 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700728
729 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700730 if password is not None:
731 password += "\n"
732 p.communicate(password)
733 if p.returncode != 0:
734 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
735
Doug Zongkereef39442009-04-02 12:14:19 -0700736
Doug Zongker37974732010-09-16 17:44:38 -0700737def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800738 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700739
Tao Bao9dd909e2017-11-14 11:27:32 -0800740 For non-AVB images, raise exception if the data is too big. Print a warning
741 if the data is nearing the maximum size.
742
743 For AVB images, the actual image size should be identical to the limit.
744
745 Args:
746 data: A string that contains all the data for the partition.
747 target: The partition name. The ".img" suffix is optional.
748 info_dict: The dict to be looked up for relevant info.
749 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700750 if target.endswith(".img"):
751 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700752 mount_point = "/" + target
753
Ying Wangf8824af2014-06-03 14:07:27 -0700754 fs_type = None
755 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700756 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700757 if mount_point == "/userdata":
758 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700759 p = info_dict["fstab"][mount_point]
760 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800761 device = p.device
762 if "/" in device:
763 device = device[device.rfind("/")+1:]
764 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700765 if not fs_type or not limit:
766 return
Doug Zongkereef39442009-04-02 12:14:19 -0700767
Andrew Boie0f9aec82012-02-14 09:32:52 -0800768 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800769 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
770 # path.
771 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
772 if size != limit:
773 raise ExternalError(
774 "Mismatching image size for %s: expected %d actual %d" % (
775 target, limit, size))
776 else:
777 pct = float(size) * 100.0 / limit
778 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
779 if pct >= 99.0:
780 raise ExternalError(msg)
781 elif pct >= 95.0:
782 print("\n WARNING: %s\n" % (msg,))
783 elif OPTIONS.verbose:
784 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700785
786
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800787def ReadApkCerts(tf_zip):
788 """Given a target_files ZipFile, parse the META/apkcerts.txt file
789 and return a {package: cert} dict."""
790 certmap = {}
791 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
792 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700793 if not line:
794 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800795 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
796 r'private_key="(.*)"$', line)
797 if m:
798 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700799 public_key_suffix_len = len(OPTIONS.public_key_suffix)
800 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800801 if cert in SPECIAL_CERT_STRINGS and not privkey:
802 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700803 elif (cert.endswith(OPTIONS.public_key_suffix) and
804 privkey.endswith(OPTIONS.private_key_suffix) and
805 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
806 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800807 else:
808 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
809 return certmap
810
811
Doug Zongkereef39442009-04-02 12:14:19 -0700812COMMON_DOCSTRING = """
813 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700814 Prepend <dir>/bin to the list of places to search for binaries
815 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700816
Doug Zongker05d3dea2009-06-22 11:32:31 -0700817 -s (--device_specific) <file>
818 Path to the python module containing device-specific
819 releasetools code.
820
Doug Zongker8bec09e2009-11-30 15:37:14 -0800821 -x (--extra) <key=value>
822 Add a key/value pair to the 'extras' dict, which device-specific
823 extension code may look at.
824
Doug Zongkereef39442009-04-02 12:14:19 -0700825 -v (--verbose)
826 Show command lines being executed.
827
828 -h (--help)
829 Display this usage message and exit.
830"""
831
832def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800833 print(docstring.rstrip("\n"))
834 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700835
836
837def ParseOptions(argv,
838 docstring,
839 extra_opts="", extra_long_opts=(),
840 extra_option_handler=None):
841 """Parse the options in argv and return any arguments that aren't
842 flags. docstring is the calling module's docstring, to be displayed
843 for errors and -h. extra_opts and extra_long_opts are for flags
844 defined by the caller, which are processed by passing them to
845 extra_option_handler."""
846
847 try:
848 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800849 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800850 ["help", "verbose", "path=", "signapk_path=",
851 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700852 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700853 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
854 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800855 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700856 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700857 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700858 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800859 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700860 sys.exit(2)
861
Doug Zongkereef39442009-04-02 12:14:19 -0700862 for o, a in opts:
863 if o in ("-h", "--help"):
864 Usage(docstring)
865 sys.exit()
866 elif o in ("-v", "--verbose"):
867 OPTIONS.verbose = True
868 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700869 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700870 elif o in ("--signapk_path",):
871 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800872 elif o in ("--signapk_shared_library_path",):
873 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700874 elif o in ("--extra_signapk_args",):
875 OPTIONS.extra_signapk_args = shlex.split(a)
876 elif o in ("--java_path",):
877 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700878 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800879 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700880 elif o in ("--public_key_suffix",):
881 OPTIONS.public_key_suffix = a
882 elif o in ("--private_key_suffix",):
883 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800884 elif o in ("--boot_signer_path",):
885 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700886 elif o in ("--boot_signer_args",):
887 OPTIONS.boot_signer_args = shlex.split(a)
888 elif o in ("--verity_signer_path",):
889 OPTIONS.verity_signer_path = a
890 elif o in ("--verity_signer_args",):
891 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700892 elif o in ("-s", "--device_specific"):
893 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800894 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800895 key, value = a.split("=", 1)
896 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700897 else:
898 if extra_option_handler is None or not extra_option_handler(o, a):
899 assert False, "unknown option \"%s\"" % (o,)
900
Doug Zongker85448772014-09-09 14:59:20 -0700901 if OPTIONS.search_path:
902 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
903 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700904
905 return args
906
907
Tao Bao4c851b12016-09-19 13:54:38 -0700908def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700909 """Make a temp file and add it to the list of things to be deleted
910 when Cleanup() is called. Return the filename."""
911 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
912 os.close(fd)
913 OPTIONS.tempfiles.append(fn)
914 return fn
915
916
Doug Zongkereef39442009-04-02 12:14:19 -0700917def Cleanup():
918 for i in OPTIONS.tempfiles:
919 if os.path.isdir(i):
920 shutil.rmtree(i)
921 else:
922 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700923
924
925class PasswordManager(object):
926 def __init__(self):
927 self.editor = os.getenv("EDITOR", None)
928 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
929
930 def GetPasswords(self, items):
931 """Get passwords corresponding to each string in 'items',
932 returning a dict. (The dict may have keys in addition to the
933 values in 'items'.)
934
935 Uses the passwords in $ANDROID_PW_FILE if available, letting the
936 user edit that file to add more needed passwords. If no editor is
937 available, or $ANDROID_PW_FILE isn't define, prompts the user
938 interactively in the ordinary way.
939 """
940
941 current = self.ReadFile()
942
943 first = True
944 while True:
945 missing = []
946 for i in items:
947 if i not in current or not current[i]:
948 missing.append(i)
949 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700950 if not missing:
951 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700952
953 for i in missing:
954 current[i] = ""
955
956 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800957 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700958 answer = raw_input("try to edit again? [y]> ").strip()
959 if answer and answer[0] not in 'yY':
960 raise RuntimeError("key passwords unavailable")
961 first = False
962
963 current = self.UpdateAndReadFile(current)
964
Dan Albert8b72aef2015-03-23 19:13:21 -0700965 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700966 """Prompt the user to enter a value (password) for each key in
967 'current' whose value is fales. Returns a new dict with all the
968 values.
969 """
970 result = {}
971 for k, v in sorted(current.iteritems()):
972 if v:
973 result[k] = v
974 else:
975 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700976 result[k] = getpass.getpass(
977 "Enter password for %s key> " % k).strip()
978 if result[k]:
979 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700980 return result
981
982 def UpdateAndReadFile(self, current):
983 if not self.editor or not self.pwfile:
984 return self.PromptResult(current)
985
986 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700987 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700988 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
989 f.write("# (Additional spaces are harmless.)\n\n")
990
991 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700992 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
993 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700994 f.write("[[[ %s ]]] %s\n" % (v, k))
995 if not v and first_line is None:
996 # position cursor on first line with no password.
997 first_line = i + 4
998 f.close()
999
1000 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1001 _, _ = p.communicate()
1002
1003 return self.ReadFile()
1004
1005 def ReadFile(self):
1006 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001007 if self.pwfile is None:
1008 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001009 try:
1010 f = open(self.pwfile, "r")
1011 for line in f:
1012 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001013 if not line or line[0] == '#':
1014 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001015 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1016 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001017 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001018 else:
1019 result[m.group(2)] = m.group(1)
1020 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001021 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001022 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001023 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001024 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001025
1026
Dan Albert8e0178d2015-01-27 15:53:15 -08001027def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1028 compress_type=None):
1029 import datetime
1030
1031 # http://b/18015246
1032 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1033 # for files larger than 2GiB. We can work around this by adjusting their
1034 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1035 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1036 # it isn't clear to me exactly what circumstances cause this).
1037 # `zipfile.write()` must be used directly to work around this.
1038 #
1039 # This mess can be avoided if we port to python3.
1040 saved_zip64_limit = zipfile.ZIP64_LIMIT
1041 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1042
1043 if compress_type is None:
1044 compress_type = zip_file.compression
1045 if arcname is None:
1046 arcname = filename
1047
1048 saved_stat = os.stat(filename)
1049
1050 try:
1051 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1052 # file to be zipped and reset it when we're done.
1053 os.chmod(filename, perms)
1054
1055 # Use a fixed timestamp so the output is repeatable.
1056 epoch = datetime.datetime.fromtimestamp(0)
1057 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1058 os.utime(filename, (timestamp, timestamp))
1059
1060 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1061 finally:
1062 os.chmod(filename, saved_stat.st_mode)
1063 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1064 zipfile.ZIP64_LIMIT = saved_zip64_limit
1065
1066
Tao Bao58c1b962015-05-20 09:32:18 -07001067def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001068 compress_type=None):
1069 """Wrap zipfile.writestr() function to work around the zip64 limit.
1070
1071 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1072 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1073 when calling crc32(bytes).
1074
1075 But it still works fine to write a shorter string into a large zip file.
1076 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1077 when we know the string won't be too long.
1078 """
1079
1080 saved_zip64_limit = zipfile.ZIP64_LIMIT
1081 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1082
1083 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1084 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001085 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001086 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001087 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001088 else:
Tao Baof3282b42015-04-01 11:21:55 -07001089 zinfo = zinfo_or_arcname
1090
1091 # If compress_type is given, it overrides the value in zinfo.
1092 if compress_type is not None:
1093 zinfo.compress_type = compress_type
1094
Tao Bao58c1b962015-05-20 09:32:18 -07001095 # If perms is given, it has a priority.
1096 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001097 # If perms doesn't set the file type, mark it as a regular file.
1098 if perms & 0o770000 == 0:
1099 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001100 zinfo.external_attr = perms << 16
1101
Tao Baof3282b42015-04-01 11:21:55 -07001102 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001103 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1104
Dan Albert8b72aef2015-03-23 19:13:21 -07001105 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001106 zipfile.ZIP64_LIMIT = saved_zip64_limit
1107
1108
1109def ZipClose(zip_file):
1110 # http://b/18015246
1111 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1112 # central directory.
1113 saved_zip64_limit = zipfile.ZIP64_LIMIT
1114 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1115
1116 zip_file.close()
1117
1118 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001119
1120
1121class DeviceSpecificParams(object):
1122 module = None
1123 def __init__(self, **kwargs):
1124 """Keyword arguments to the constructor become attributes of this
1125 object, which is passed to all functions in the device-specific
1126 module."""
1127 for k, v in kwargs.iteritems():
1128 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001129 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001130
1131 if self.module is None:
1132 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001133 if not path:
1134 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001135 try:
1136 if os.path.isdir(path):
1137 info = imp.find_module("releasetools", [path])
1138 else:
1139 d, f = os.path.split(path)
1140 b, x = os.path.splitext(f)
1141 if x == ".py":
1142 f = b
1143 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001144 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001145 self.module = imp.load_module("device_specific", *info)
1146 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001147 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001148
1149 def _DoCall(self, function_name, *args, **kwargs):
1150 """Call the named function in the device-specific module, passing
1151 the given args and kwargs. The first argument to the call will be
1152 the DeviceSpecific object itself. If there is no module, or the
1153 module does not define the function, return the value of the
1154 'default' kwarg (which itself defaults to None)."""
1155 if self.module is None or not hasattr(self.module, function_name):
1156 return kwargs.get("default", None)
1157 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1158
1159 def FullOTA_Assertions(self):
1160 """Called after emitting the block of assertions at the top of a
1161 full OTA package. Implementations can add whatever additional
1162 assertions they like."""
1163 return self._DoCall("FullOTA_Assertions")
1164
Doug Zongkere5ff5902012-01-17 10:55:37 -08001165 def FullOTA_InstallBegin(self):
1166 """Called at the start of full OTA installation."""
1167 return self._DoCall("FullOTA_InstallBegin")
1168
Doug Zongker05d3dea2009-06-22 11:32:31 -07001169 def FullOTA_InstallEnd(self):
1170 """Called at the end of full OTA installation; typically this is
1171 used to install the image for the device's baseband processor."""
1172 return self._DoCall("FullOTA_InstallEnd")
1173
1174 def IncrementalOTA_Assertions(self):
1175 """Called after emitting the block of assertions at the top of an
1176 incremental OTA package. Implementations can add whatever
1177 additional assertions they like."""
1178 return self._DoCall("IncrementalOTA_Assertions")
1179
Doug Zongkere5ff5902012-01-17 10:55:37 -08001180 def IncrementalOTA_VerifyBegin(self):
1181 """Called at the start of the verification phase of incremental
1182 OTA installation; additional checks can be placed here to abort
1183 the script before any changes are made."""
1184 return self._DoCall("IncrementalOTA_VerifyBegin")
1185
Doug Zongker05d3dea2009-06-22 11:32:31 -07001186 def IncrementalOTA_VerifyEnd(self):
1187 """Called at the end of the verification phase of incremental OTA
1188 installation; additional checks can be placed here to abort the
1189 script before any changes are made."""
1190 return self._DoCall("IncrementalOTA_VerifyEnd")
1191
Doug Zongkere5ff5902012-01-17 10:55:37 -08001192 def IncrementalOTA_InstallBegin(self):
1193 """Called at the start of incremental OTA installation (after
1194 verification is complete)."""
1195 return self._DoCall("IncrementalOTA_InstallBegin")
1196
Doug Zongker05d3dea2009-06-22 11:32:31 -07001197 def IncrementalOTA_InstallEnd(self):
1198 """Called at the end of incremental OTA installation; typically
1199 this is used to install the image for the device's baseband
1200 processor."""
1201 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001202
Tao Bao9bc6bb22015-11-09 16:58:28 -08001203 def VerifyOTA_Assertions(self):
1204 return self._DoCall("VerifyOTA_Assertions")
1205
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001206class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001207 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001208 self.name = name
1209 self.data = data
1210 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001211 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001212 self.sha1 = sha1(data).hexdigest()
1213
1214 @classmethod
1215 def FromLocalFile(cls, name, diskname):
1216 f = open(diskname, "rb")
1217 data = f.read()
1218 f.close()
1219 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001220
1221 def WriteToTemp(self):
1222 t = tempfile.NamedTemporaryFile()
1223 t.write(self.data)
1224 t.flush()
1225 return t
1226
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001227 def WriteToDir(self, d):
1228 with open(os.path.join(d, self.name), "wb") as fp:
1229 fp.write(self.data)
1230
Geremy Condra36bd3652014-02-06 19:45:10 -08001231 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001232 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001233
1234DIFF_PROGRAM_BY_EXT = {
1235 ".gz" : "imgdiff",
1236 ".zip" : ["imgdiff", "-z"],
1237 ".jar" : ["imgdiff", "-z"],
1238 ".apk" : ["imgdiff", "-z"],
1239 ".img" : "imgdiff",
1240 }
1241
1242class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001243 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001244 self.tf = tf
1245 self.sf = sf
1246 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001247 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001248
1249 def ComputePatch(self):
1250 """Compute the patch (as a string of data) needed to turn sf into
1251 tf. Returns the same tuple as GetPatch()."""
1252
1253 tf = self.tf
1254 sf = self.sf
1255
Doug Zongker24cd2802012-08-14 16:36:15 -07001256 if self.diff_program:
1257 diff_program = self.diff_program
1258 else:
1259 ext = os.path.splitext(tf.name)[1]
1260 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001261
1262 ttemp = tf.WriteToTemp()
1263 stemp = sf.WriteToTemp()
1264
1265 ext = os.path.splitext(tf.name)[1]
1266
1267 try:
1268 ptemp = tempfile.NamedTemporaryFile()
1269 if isinstance(diff_program, list):
1270 cmd = copy.copy(diff_program)
1271 else:
1272 cmd = [diff_program]
1273 cmd.append(stemp.name)
1274 cmd.append(ttemp.name)
1275 cmd.append(ptemp.name)
1276 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001277 err = []
1278 def run():
1279 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001280 if e:
1281 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001282 th = threading.Thread(target=run)
1283 th.start()
1284 th.join(timeout=300) # 5 mins
1285 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001286 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001287 p.terminate()
1288 th.join(5)
1289 if th.is_alive():
1290 p.kill()
1291 th.join()
1292
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001293 if err or p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001294 print("WARNING: failure running %s:\n%s\n" % (
1295 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001296 self.patch = None
1297 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001298 diff = ptemp.read()
1299 finally:
1300 ptemp.close()
1301 stemp.close()
1302 ttemp.close()
1303
1304 self.patch = diff
1305 return self.tf, self.sf, self.patch
1306
1307
1308 def GetPatch(self):
1309 """Return a tuple (target_file, source_file, patch_data).
1310 patch_data may be None if ComputePatch hasn't been called, or if
1311 computing the patch failed."""
1312 return self.tf, self.sf, self.patch
1313
1314
1315def ComputeDifferences(diffs):
1316 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001317 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001318
1319 # Do the largest files first, to try and reduce the long-pole effect.
1320 by_size = [(i.tf.size, i) for i in diffs]
1321 by_size.sort(reverse=True)
1322 by_size = [i[1] for i in by_size]
1323
1324 lock = threading.Lock()
1325 diff_iter = iter(by_size) # accessed under lock
1326
1327 def worker():
1328 try:
1329 lock.acquire()
1330 for d in diff_iter:
1331 lock.release()
1332 start = time.time()
1333 d.ComputePatch()
1334 dur = time.time() - start
1335 lock.acquire()
1336
1337 tf, sf, patch = d.GetPatch()
1338 if sf.name == tf.name:
1339 name = tf.name
1340 else:
1341 name = "%s (%s)" % (tf.name, sf.name)
1342 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001343 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001344 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001345 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1346 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001347 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001348 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001349 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001350 raise
1351
1352 # start worker threads; wait for them all to finish.
1353 threads = [threading.Thread(target=worker)
1354 for i in range(OPTIONS.worker_threads)]
1355 for th in threads:
1356 th.start()
1357 while threads:
1358 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001359
1360
Dan Albert8b72aef2015-03-23 19:13:21 -07001361class BlockDifference(object):
1362 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001363 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001364 self.tgt = tgt
1365 self.src = src
1366 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001367 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001368 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001369
Tao Baodd2a5892015-03-12 12:32:37 -07001370 if version is None:
1371 version = 1
1372 if OPTIONS.info_dict:
1373 version = max(
1374 int(i) for i in
1375 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001376 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001377 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001378
1379 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001380 version=self.version,
1381 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001382 tmpdir = tempfile.mkdtemp()
1383 OPTIONS.tempfiles.append(tmpdir)
1384 self.path = os.path.join(tmpdir, partition)
1385 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001386 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001387 self.touched_src_ranges = b.touched_src_ranges
1388 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001389
Tao Baoaac4ad52015-10-16 15:26:34 -07001390 if src is None:
1391 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1392 else:
1393 _, self.device = GetTypeAndDevice("/" + partition,
1394 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001395
Tao Baod8d14be2016-02-04 14:26:02 -08001396 @property
1397 def required_cache(self):
1398 return self._required_cache
1399
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001400 def WriteScript(self, script, output_zip, progress=None):
1401 if not self.src:
1402 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001403 script.Print("Patching %s image unconditionally..." % (self.partition,))
1404 else:
1405 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001406
Dan Albert8b72aef2015-03-23 19:13:21 -07001407 if progress:
1408 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001409 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001410 if OPTIONS.verify:
1411 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001412
Tao Bao9bc6bb22015-11-09 16:58:28 -08001413 def WriteStrictVerifyScript(self, script):
1414 """Verify all the blocks in the care_map, including clobbered blocks.
1415
1416 This differs from the WriteVerifyScript() function: a) it prints different
1417 error messages; b) it doesn't allow half-way updated images to pass the
1418 verification."""
1419
1420 partition = self.partition
1421 script.Print("Verifying %s..." % (partition,))
1422 ranges = self.tgt.care_map
1423 ranges_str = ranges.to_string_raw()
1424 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1425 'ui_print(" Verified.") || '
1426 'ui_print("\\"%s\\" has unexpected contents.");' % (
1427 self.device, ranges_str,
1428 self.tgt.TotalSha1(include_clobbered_blocks=True),
1429 self.device))
1430 script.AppendExtra("")
1431
Tao Baod522bdc2016-04-12 15:53:16 -07001432 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001433 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001434
1435 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001436 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001437 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001438
1439 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001440 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001441 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001442 ranges = self.touched_src_ranges
1443 expected_sha1 = self.touched_src_sha1
1444 else:
1445 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1446 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001447
1448 # No blocks to be checked, skipping.
1449 if not ranges:
1450 return
1451
Tao Bao5ece99d2015-05-12 11:42:31 -07001452 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001453 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1454 'block_image_verify("%s", '
1455 'package_extract_file("%s.transfer.list"), '
1456 '"%s.new.dat", "%s.patch.dat")) then') % (
1457 self.device, ranges_str, expected_sha1,
1458 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001459 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001460 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001461
Tianjie Xufc3422a2015-12-15 11:53:59 -08001462 if self.version >= 4:
1463
1464 # Bug: 21124327
1465 # When generating incrementals for the system and vendor partitions in
1466 # version 4 or newer, explicitly check the first block (which contains
1467 # the superblock) of the partition to see if it's what we expect. If
1468 # this check fails, give an explicit log message about the partition
1469 # having been remounted R/W (the most likely explanation).
1470 if self.check_first_block:
1471 script.AppendExtra('check_first_block("%s");' % (self.device,))
1472
1473 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001474 if partition == "system":
1475 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1476 else:
1477 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001478 script.AppendExtra((
1479 'ifelse (block_image_recover("{device}", "{ranges}") && '
1480 'block_image_verify("{device}", '
1481 'package_extract_file("{partition}.transfer.list"), '
1482 '"{partition}.new.dat", "{partition}.patch.dat"), '
1483 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001484 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001485 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001486 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001487
Tao Baodd2a5892015-03-12 12:32:37 -07001488 # Abort the OTA update. Note that the incremental OTA cannot be applied
1489 # even if it may match the checksum of the target partition.
1490 # a) If version < 3, operations like move and erase will make changes
1491 # unconditionally and damage the partition.
1492 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001493 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001494 if partition == "system":
1495 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1496 else:
1497 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1498 script.AppendExtra((
1499 'abort("E%d: %s partition has unexpected contents");\n'
1500 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001501
Tao Bao5fcaaef2015-06-01 13:40:49 -07001502 def _WritePostInstallVerifyScript(self, script):
1503 partition = self.partition
1504 script.Print('Verifying the updated %s image...' % (partition,))
1505 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1506 ranges = self.tgt.care_map
1507 ranges_str = ranges.to_string_raw()
1508 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1509 self.device, ranges_str,
1510 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001511
1512 # Bug: 20881595
1513 # Verify that extended blocks are really zeroed out.
1514 if self.tgt.extended:
1515 ranges_str = self.tgt.extended.to_string_raw()
1516 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1517 self.device, ranges_str,
1518 self._HashZeroBlocks(self.tgt.extended.size())))
1519 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001520 if partition == "system":
1521 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1522 else:
1523 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001524 script.AppendExtra(
1525 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001526 ' abort("E%d: %s partition has unexpected non-zero contents after '
1527 'OTA update");\n'
1528 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001529 else:
1530 script.Print('Verified the updated %s image.' % (partition,))
1531
Tianjie Xu209db462016-05-24 17:34:52 -07001532 if partition == "system":
1533 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1534 else:
1535 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1536
Tao Bao5fcaaef2015-06-01 13:40:49 -07001537 script.AppendExtra(
1538 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001539 ' abort("E%d: %s partition has unexpected contents after OTA '
1540 'update");\n'
1541 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001542
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001543 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001544 ZipWrite(output_zip,
1545 '{}.transfer.list'.format(self.path),
1546 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001547
1548 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1549 # almost triples the compression time but doesn't further reduce the size too much.
1550 # For a typical 1.8G system.new.dat
1551 # zip | brotli(quality 6) | brotli(quality 9)
1552 # compressed_size: 942M | 869M (~8% reduced) | 854M
1553 # compression_time: 75s | 265s | 719s
1554 # decompression_time: 15s | 25s | 25s
1555
1556 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001557 brotli_cmd = ['brotli', '--quality=6',
1558 '--output={}.new.dat.br'.format(self.path),
1559 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001560 print("Compressing {}.new.dat with brotli".format(self.partition))
Alex Deymob10e07a2017-11-09 23:53:42 +01001561 p = Run(brotli_cmd, stdout=subprocess.PIPE)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001562 p.communicate()
1563 assert p.returncode == 0,\
1564 'compression of {}.new.dat failed'.format(self.partition)
1565
1566 new_data_name = '{}.new.dat.br'.format(self.partition)
1567 ZipWrite(output_zip,
1568 '{}.new.dat.br'.format(self.path),
1569 new_data_name,
1570 compress_type=zipfile.ZIP_STORED)
1571 else:
1572 new_data_name = '{}.new.dat'.format(self.partition)
1573 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1574
Dan Albert8e0178d2015-01-27 15:53:15 -08001575 ZipWrite(output_zip,
1576 '{}.patch.dat'.format(self.path),
1577 '{}.patch.dat'.format(self.partition),
1578 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001579
Tianjie Xu209db462016-05-24 17:34:52 -07001580 if self.partition == "system":
1581 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1582 else:
1583 code = ErrorCode.VENDOR_UPDATE_FAILURE
1584
Dan Albert8e0178d2015-01-27 15:53:15 -08001585 call = ('block_image_update("{device}", '
1586 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001587 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001588 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001589 device=self.device, partition=self.partition,
1590 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001591 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001592
Dan Albert8b72aef2015-03-23 19:13:21 -07001593 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001594 data = source.ReadRangeSet(ranges)
1595 ctx = sha1()
1596
1597 for p in data:
1598 ctx.update(p)
1599
1600 return ctx.hexdigest()
1601
Tao Baoe9b61912015-07-09 17:37:49 -07001602 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1603 """Return the hash value for all zero blocks."""
1604 zero_block = '\x00' * 4096
1605 ctx = sha1()
1606 for _ in range(num_blocks):
1607 ctx.update(zero_block)
1608
1609 return ctx.hexdigest()
1610
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001611
1612DataImage = blockimgdiff.DataImage
1613
Doug Zongker96a57e72010-09-26 14:57:41 -07001614# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001615PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001616 "ext4": "EMMC",
1617 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001618 "f2fs": "EMMC",
1619 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001620}
Doug Zongker96a57e72010-09-26 14:57:41 -07001621
1622def GetTypeAndDevice(mount_point, info):
1623 fstab = info["fstab"]
1624 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001625 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1626 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001627 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001628 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001629
1630
1631def ParseCertificate(data):
1632 """Parse a PEM-format certificate."""
1633 cert = []
1634 save = False
1635 for line in data.split("\n"):
1636 if "--END CERTIFICATE--" in line:
1637 break
1638 if save:
1639 cert.append(line)
1640 if "--BEGIN CERTIFICATE--" in line:
1641 save = True
1642 cert = "".join(cert).decode('base64')
1643 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001644
Doug Zongker412c02f2014-02-13 10:58:24 -08001645def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1646 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001647 """Generate a binary patch that creates the recovery image starting
1648 with the boot image. (Most of the space in these images is just the
1649 kernel, which is identical for the two, so the resulting patch
1650 should be efficient.) Add it to the output zip, along with a shell
1651 script that is run from init.rc on first boot to actually do the
1652 patching and install the new recovery image.
1653
1654 recovery_img and boot_img should be File objects for the
1655 corresponding images. info should be the dictionary returned by
1656 common.LoadInfoDict() on the input target_files.
1657 """
1658
Doug Zongker412c02f2014-02-13 10:58:24 -08001659 if info_dict is None:
1660 info_dict = OPTIONS.info_dict
1661
Tao Baof2cffbd2015-07-22 12:33:18 -07001662 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001663
Tao Baof2cffbd2015-07-22 12:33:18 -07001664 if full_recovery_image:
1665 output_sink("etc/recovery.img", recovery_img.data)
1666
1667 else:
1668 diff_program = ["imgdiff"]
1669 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1670 if os.path.exists(path):
1671 diff_program.append("-b")
1672 diff_program.append(path)
1673 bonus_args = "-b /system/etc/recovery-resource.dat"
1674 else:
1675 bonus_args = ""
1676
1677 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1678 _, _, patch = d.ComputePatch()
1679 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001680
Dan Albertebb19aa2015-03-27 19:11:53 -07001681 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001682 # The following GetTypeAndDevice()s need to use the path in the target
1683 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001684 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1685 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1686 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001687 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001688
Tao Baof2cffbd2015-07-22 12:33:18 -07001689 if full_recovery_image:
1690 sh = """#!/system/bin/sh
1691if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1692 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"
1693else
1694 log -t recovery "Recovery image already installed"
1695fi
1696""" % {'type': recovery_type,
1697 'device': recovery_device,
1698 'sha1': recovery_img.sha1,
1699 'size': recovery_img.size}
1700 else:
1701 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001702if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1703 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"
1704else
1705 log -t recovery "Recovery image already installed"
1706fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001707""" % {'boot_size': boot_img.size,
1708 'boot_sha1': boot_img.sha1,
1709 'recovery_size': recovery_img.size,
1710 'recovery_sha1': recovery_img.sha1,
1711 'boot_type': boot_type,
1712 'boot_device': boot_device,
1713 'recovery_type': recovery_type,
1714 'recovery_device': recovery_device,
1715 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001716
1717 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001718 # in the L release.
1719 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001720
Tao Bao89fbb0f2017-01-10 10:47:58 -08001721 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001722
1723 output_sink(sh_location, sh)