blob: 34c334e40f5e44ec514f1ff862a2afe50650b75f [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
28import subprocess
29import sys
30import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070031import threading
32import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070033import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070034
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070035import blockimgdiff
36
Tao Baof3282b42015-04-01 11:21:55 -070037from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080038
Doug Zongkereef39442009-04-02 12:14:19 -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
Tianjie Xu209db462016-05-24 17:34:52 -070079class ErrorCode(object):
80 """Define error_codes for failures that happen during the actual
81 update package installation.
82
83 Error codes 0-999 are reserved for failures before the package
84 installation (i.e. low battery, package verification failure).
85 Detailed code in 'bootable/recovery/error_code.h' """
86
87 SYSTEM_VERIFICATION_FAILURE = 1000
88 SYSTEM_UPDATE_FAILURE = 1001
89 SYSTEM_UNEXPECTED_CONTENTS = 1002
90 SYSTEM_NONZERO_CONTENTS = 1003
91 SYSTEM_RECOVER_FAILURE = 1004
92 VENDOR_VERIFICATION_FAILURE = 2000
93 VENDOR_UPDATE_FAILURE = 2001
94 VENDOR_UNEXPECTED_CONTENTS = 2002
95 VENDOR_NONZERO_CONTENTS = 2003
96 VENDOR_RECOVER_FAILURE = 2004
97 OEM_PROP_MISMATCH = 3000
98 FINGERPRINT_MISMATCH = 3001
99 THUMBPRINT_MISMATCH = 3002
100 OLDER_BUILD = 3003
101 DEVICE_MISMATCH = 3004
102 BAD_PATCH_FILE = 3005
103 INSUFFICIENT_CACHE_SPACE = 3006
104 TUNE_PARTITION_FAILURE = 3007
105 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800106
Dan Albert8b72aef2015-03-23 19:13:21 -0700107class ExternalError(RuntimeError):
108 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700109
110
Tao Bao39451582017-05-04 11:10:47 -0700111def Run(args, verbose=None, **kwargs):
112 """Create and return a subprocess.Popen object.
113
114 Caller can specify if the command line should be printed. The global
115 OPTIONS.verbose will be used if not specified.
116 """
117 if verbose is None:
118 verbose = OPTIONS.verbose
119 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800120 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700121 return subprocess.Popen(args, **kwargs)
122
123
Ying Wang7e6d4e42010-12-13 16:25:36 -0800124def CloseInheritedPipes():
125 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
126 before doing other work."""
127 if platform.system() != "Darwin":
128 return
129 for d in range(3, 1025):
130 try:
131 stat = os.fstat(d)
132 if stat is not None:
133 pipebit = stat[0] & 0x1000
134 if pipebit != 0:
135 os.close(d)
136 except OSError:
137 pass
138
139
Tao Bao2c15d9e2015-07-09 11:51:16 -0700140def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700141 """Read and parse the META/misc_info.txt key/value pairs from the
142 input target files and return a dict."""
143
Doug Zongkerc9253822014-02-04 12:17:58 -0800144 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700145 if isinstance(input_file, zipfile.ZipFile):
146 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800147 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700148 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800149 try:
150 with open(path) as f:
151 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700152 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800153 if e.errno == errno.ENOENT:
154 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800155
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700156 try:
Michael Runge6e836112014-04-15 17:40:21 -0700157 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700158 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800159 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700160
Tao Bao6cd54732017-02-27 15:12:05 -0800161 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800162 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800163
Tao Bao84e75682015-07-19 02:38:53 -0700164 # A few properties are stored as links to the files in the out/ directory.
165 # It works fine with the build system. However, they are no longer available
166 # when (re)generating from target_files zip. If input_dir is not None, we
167 # are doing repacking. Redirect those properties to the actual files in the
168 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700169 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400170 # We carry a copy of file_contexts.bin under META/. If not available,
171 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700172 # to build images than the one running on device, such as when enabling
173 # system_root_image. In that case, we must have the one for image
174 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700175 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
176 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700177 if d.get("system_root_image") == "true":
178 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700179 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700180 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700181 if not os.path.exists(fc_config):
182 fc_config = None
183
184 if fc_config:
185 d["selinux_fc"] = fc_config
186
Tao Bao84e75682015-07-19 02:38:53 -0700187 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
188 if d.get("system_root_image") == "true":
189 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
190 d["ramdisk_fs_config"] = os.path.join(
191 input_dir, "META", "root_filesystem_config.txt")
192
Tao Baof54216f2016-03-29 15:12:37 -0700193 # Redirect {system,vendor}_base_fs_file.
194 if "system_base_fs_file" in d:
195 basename = os.path.basename(d["system_base_fs_file"])
196 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700197 if os.path.exists(system_base_fs_file):
198 d["system_base_fs_file"] = system_base_fs_file
199 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800200 print("Warning: failed to find system base fs file: %s" % (
201 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700202 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700203
204 if "vendor_base_fs_file" in d:
205 basename = os.path.basename(d["vendor_base_fs_file"])
206 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700207 if os.path.exists(vendor_base_fs_file):
208 d["vendor_base_fs_file"] = vendor_base_fs_file
209 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800210 print("Warning: failed to find vendor base fs file: %s" % (
211 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700212 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700213
Doug Zongker37974732010-09-16 17:44:38 -0700214 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800215 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700216 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700217 if not line:
218 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700219 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700220 if not value:
221 continue
Doug Zongker37974732010-09-16 17:44:38 -0700222 if name == "blocksize":
223 d[name] = value
224 else:
225 d[name + "_size"] = value
226 except KeyError:
227 pass
228
229 def makeint(key):
230 if key in d:
231 d[key] = int(d[key], 0)
232
233 makeint("recovery_api_version")
234 makeint("blocksize")
235 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700236 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700237 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700238 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700239 makeint("recovery_size")
240 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800241 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700242
Tianjie Xucfa86222016-03-07 16:31:19 -0800243 system_root_image = d.get("system_root_image", None) == "true"
244 if d.get("no_recovery", None) != "true":
245 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800246 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800247 recovery_fstab_path, system_root_image)
248 elif d.get("recovery_as_boot", None) == "true":
249 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
250 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
251 recovery_fstab_path, system_root_image)
252 else:
253 d["fstab"] = None
254
Doug Zongkerc9253822014-02-04 12:17:58 -0800255 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700256 return d
257
Tao Baod1de6f32017-03-01 16:38:48 -0800258
Doug Zongkerc9253822014-02-04 12:17:58 -0800259def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700260 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800261 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700262 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800263 print("Warning: could not find SYSTEM/build.prop in %s" % (zip,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700264 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700265 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700266
Tao Baod1de6f32017-03-01 16:38:48 -0800267
Michael Runge6e836112014-04-15 17:40:21 -0700268def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700269 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700270 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700271 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700272 if not line or line.startswith("#"):
273 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700274 if "=" in line:
275 name, value = line.split("=", 1)
276 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700277 return d
278
Tao Baod1de6f32017-03-01 16:38:48 -0800279
Tianjie Xucfa86222016-03-07 16:31:19 -0800280def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
281 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700282 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800283 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700284 self.mount_point = mount_point
285 self.fs_type = fs_type
286 self.device = device
287 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700288 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700289
290 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800291 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700292 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800293 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700294 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700295
Tao Baod1de6f32017-03-01 16:38:48 -0800296 assert fstab_version == 2
297
298 d = {}
299 for line in data.split("\n"):
300 line = line.strip()
301 if not line or line.startswith("#"):
302 continue
303
304 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
305 pieces = line.split()
306 if len(pieces) != 5:
307 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
308
309 # Ignore entries that are managed by vold.
310 options = pieces[4]
311 if "voldmanaged=" in options:
312 continue
313
314 # It's a good line, parse it.
315 length = 0
316 options = options.split(",")
317 for i in options:
318 if i.startswith("length="):
319 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800320 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800321 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700322 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800323
Tao Baod1de6f32017-03-01 16:38:48 -0800324 mount_flags = pieces[3]
325 # Honor the SELinux context if present.
326 context = None
327 for i in mount_flags.split(","):
328 if i.startswith("context="):
329 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800330
Tao Baod1de6f32017-03-01 16:38:48 -0800331 mount_point = pieces[1]
332 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
333 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800334
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700335 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700336 # system. Other areas assume system is always at "/system" so point /system
337 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700338 if system_root_image:
339 assert not d.has_key("/system") and d.has_key("/")
340 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700341 return d
342
343
Doug Zongker37974732010-09-16 17:44:38 -0700344def DumpInfoDict(d):
345 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800346 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700347
Dan Albert8b72aef2015-03-23 19:13:21 -0700348
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800349def AppendAVBSigningArgs(cmd, partition):
350 """Append signing arguments for avbtool."""
351 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
352 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
353 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
354 if key_path and algorithm:
355 cmd.extend(["--key", key_path, "--algorithm", algorithm])
356
357
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700358def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800359 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700360 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700361
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700362 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800363 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
364 we are building a two-step special image (i.e. building a recovery image to
365 be loaded into /boot in two-step OTAs).
366
367 Return the image data, or None if sourcedir does not appear to contains files
368 for building the requested image.
369 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700370
371 def make_ramdisk():
372 ramdisk_img = tempfile.NamedTemporaryFile()
373
374 if os.access(fs_config_file, os.F_OK):
375 cmd = ["mkbootfs", "-f", fs_config_file,
376 os.path.join(sourcedir, "RAMDISK")]
377 else:
378 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
379 p1 = Run(cmd, stdout=subprocess.PIPE)
380 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
381
382 p2.wait()
383 p1.wait()
384 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
385 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
386
387 return ramdisk_img
388
389 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
390 return None
391
392 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700393 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700394
Doug Zongkerd5131602012-08-02 14:46:42 -0700395 if info_dict is None:
396 info_dict = OPTIONS.info_dict
397
Doug Zongkereef39442009-04-02 12:14:19 -0700398 img = tempfile.NamedTemporaryFile()
399
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700400 if has_ramdisk:
401 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700402
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800403 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
404 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
405
406 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700407
Benoit Fradina45a8682014-07-14 21:00:43 +0200408 fn = os.path.join(sourcedir, "second")
409 if os.access(fn, os.F_OK):
410 cmd.append("--second")
411 cmd.append(fn)
412
Doug Zongker171f1cd2009-06-15 22:36:37 -0700413 fn = os.path.join(sourcedir, "cmdline")
414 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700415 cmd.append("--cmdline")
416 cmd.append(open(fn).read().rstrip("\n"))
417
418 fn = os.path.join(sourcedir, "base")
419 if os.access(fn, os.F_OK):
420 cmd.append("--base")
421 cmd.append(open(fn).read().rstrip("\n"))
422
Ying Wang4de6b5b2010-08-25 14:29:34 -0700423 fn = os.path.join(sourcedir, "pagesize")
424 if os.access(fn, os.F_OK):
425 cmd.append("--pagesize")
426 cmd.append(open(fn).read().rstrip("\n"))
427
Doug Zongkerd5131602012-08-02 14:46:42 -0700428 args = info_dict.get("mkbootimg_args", None)
429 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700430 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700431
Sami Tolvanen3303d902016-03-15 16:49:30 +0000432 args = info_dict.get("mkbootimg_version_args", None)
433 if args and args.strip():
434 cmd.extend(shlex.split(args))
435
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700436 if has_ramdisk:
437 cmd.extend(["--ramdisk", ramdisk_img.name])
438
Tao Baod95e9fd2015-03-29 23:07:41 -0700439 img_unsigned = None
440 if info_dict.get("vboot", None):
441 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700442 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700443 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700444 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700445
446 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700447 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700448 assert p.returncode == 0, "mkbootimg of %s image failed" % (
449 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700450
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100451 if (info_dict.get("boot_signer", None) == "true" and
452 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800453 # Hard-code the path as "/boot" for two-step special recovery image (which
454 # will be loaded into /boot during the two-step OTA).
455 if two_step_image:
456 path = "/boot"
457 else:
458 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700459 cmd = [OPTIONS.boot_signer_path]
460 cmd.extend(OPTIONS.boot_signer_args)
461 cmd.extend([path, img.name,
462 info_dict["verity_key"] + ".pk8",
463 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700464 p = Run(cmd, stdout=subprocess.PIPE)
465 p.communicate()
466 assert p.returncode == 0, "boot_signer of %s image failed" % path
467
Tao Baod95e9fd2015-03-29 23:07:41 -0700468 # Sign the image if vboot is non-empty.
469 elif info_dict.get("vboot", None):
470 path = "/" + os.path.basename(sourcedir).lower()
471 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800472 # We have switched from the prebuilt futility binary to using the tool
473 # (futility-host) built from the source. Override the setting in the old
474 # TF.zip.
475 futility = info_dict["futility"]
476 if futility.startswith("prebuilts/"):
477 futility = "futility-host"
478 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700479 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700480 info_dict["vboot_key"] + ".vbprivk",
481 info_dict["vboot_subkey"] + ".vbprivk",
482 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700483 img.name]
484 p = Run(cmd, stdout=subprocess.PIPE)
485 p.communicate()
486 assert p.returncode == 0, "vboot_signer of %s image failed" % path
487
Tao Baof3282b42015-04-01 11:21:55 -0700488 # Clean up the temp files.
489 img_unsigned.close()
490 img_keyblock.close()
491
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400492 # AVB: if enabled, calculate and add hash to boot.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800493 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700494 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
495 part_size = info_dict["boot_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400496 cmd = [avbtool, "add_hash_footer", "--image", img.name,
497 "--partition_size", str(part_size), "--partition_name", "boot"]
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800498 AppendAVBSigningArgs(cmd, "boot")
499 args = info_dict.get("avb_boot_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400500 if args and args.strip():
501 cmd.extend(shlex.split(args))
502 p = Run(cmd, stdout=subprocess.PIPE)
503 p.communicate()
504 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
505 os.path.basename(OPTIONS.input_tmp))
David Zeuthend995f4b2016-01-29 16:59:17 -0500506
507 img.seek(os.SEEK_SET, 0)
508 data = img.read()
509
510 if has_ramdisk:
511 ramdisk_img.close()
512 img.close()
513
514 return data
515
516
Doug Zongkerd5131602012-08-02 14:46:42 -0700517def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800518 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700519 """Return a File object with the desired bootable image.
520
521 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
522 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
523 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700524
Doug Zongker55d93282011-01-25 17:03:34 -0800525 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
526 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800527 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800528 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700529
530 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
531 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800532 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700533 return File.FromLocalFile(name, prebuilt_path)
534
Tao Bao89fbb0f2017-01-10 10:47:58 -0800535 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700536
537 if info_dict is None:
538 info_dict = OPTIONS.info_dict
539
540 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800541 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
542 # for recovery.
543 has_ramdisk = (info_dict.get("system_root_image") != "true" or
544 prebuilt_name != "boot.img" or
545 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700546
Doug Zongker6f1d0312014-08-22 08:07:12 -0700547 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400548 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
549 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800550 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700551 if data:
552 return File(name, data)
553 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800554
Doug Zongkereef39442009-04-02 12:14:19 -0700555
Narayan Kamatha07bf042017-08-14 14:49:21 +0100556def Gunzip(in_filename, out_filename):
557 """Gunzip the given gzip compressed file to a given output file.
558 """
559 with gzip.open(in_filename, "rb") as in_file, open(out_filename, "wb") as out_file:
560 shutil.copyfileobj(in_file, out_file)
561
562
Doug Zongker75f17362009-12-08 13:46:44 -0800563def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800564 """Unzip the given archive into a temporary directory and return the name.
565
566 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
567 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
568
569 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
570 main file), open for reading.
571 """
Doug Zongkereef39442009-04-02 12:14:19 -0700572
573 tmp = tempfile.mkdtemp(prefix="targetfiles-")
574 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800575
576 def unzip_to_dir(filename, dirname):
577 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
578 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800579 cmd.extend(pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800580 p = Run(cmd, stdout=subprocess.PIPE)
581 p.communicate()
582 if p.returncode != 0:
583 raise ExternalError("failed to unzip input target-files \"%s\"" %
584 (filename,))
585
586 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
587 if m:
588 unzip_to_dir(m.group(1), tmp)
589 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
590 filename = m.group(1)
591 else:
592 unzip_to_dir(filename, tmp)
593
594 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700595
596
597def GetKeyPasswords(keylist):
598 """Given a list of keys, prompt the user to enter passwords for
599 those which require them. Return a {key: password} dict. password
600 will be None if the key has no password."""
601
Doug Zongker8ce7c252009-05-22 13:34:54 -0700602 no_passwords = []
603 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700604 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700605 devnull = open("/dev/null", "w+b")
606 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800607 # We don't need a password for things that aren't really keys.
608 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700609 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700610 continue
611
T.R. Fullhart37e10522013-03-18 10:31:26 -0700612 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700613 "-inform", "DER", "-nocrypt"],
614 stdin=devnull.fileno(),
615 stdout=devnull.fileno(),
616 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700617 p.communicate()
618 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700619 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700620 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700621 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700622 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
623 "-inform", "DER", "-passin", "pass:"],
624 stdin=devnull.fileno(),
625 stdout=devnull.fileno(),
626 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700627 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700628 if p.returncode == 0:
629 # Encrypted key with empty string as password.
630 key_passwords[k] = ''
631 elif stderr.startswith('Error decrypting key'):
632 # Definitely encrypted key.
633 # It would have said "Error reading key" if it didn't parse correctly.
634 need_passwords.append(k)
635 else:
636 # Potentially, a type of key that openssl doesn't understand.
637 # We'll let the routines in signapk.jar handle it.
638 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700639 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700640
T.R. Fullhart37e10522013-03-18 10:31:26 -0700641 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700642 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700643 return key_passwords
644
645
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800646def GetMinSdkVersion(apk_name):
647 """Get the minSdkVersion delared in the APK. This can be both a decimal number
648 (API Level) or a codename.
649 """
650
651 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
652 output, err = p.communicate()
653 if err:
654 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
655 % (p.returncode,))
656
657 for line in output.split("\n"):
658 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
659 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
660 if m:
661 return m.group(1)
662 raise ExternalError("No minSdkVersion returned by aapt")
663
664
665def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
666 """Get the minSdkVersion declared in the APK as a number (API Level). If
667 minSdkVersion is set to a codename, it is translated to a number using the
668 provided map.
669 """
670
671 version = GetMinSdkVersion(apk_name)
672 try:
673 return int(version)
674 except ValueError:
675 # Not a decimal number. Codename?
676 if version in codename_to_api_level_map:
677 return codename_to_api_level_map[version]
678 else:
679 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
680 % (version, codename_to_api_level_map))
681
682
683def SignFile(input_name, output_name, key, password, min_api_level=None,
684 codename_to_api_level_map=dict(),
685 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700686 """Sign the input_name zip/jar/apk, producing output_name. Use the
687 given key and password (the latter may be None if the key does not
688 have a password.
689
Doug Zongker951495f2009-08-14 12:44:19 -0700690 If whole_file is true, use the "-w" option to SignApk to embed a
691 signature that covers the whole file in the archive comment of the
692 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800693
694 min_api_level is the API Level (int) of the oldest platform this file may end
695 up on. If not specified for an APK, the API Level is obtained by interpreting
696 the minSdkVersion attribute of the APK's AndroidManifest.xml.
697
698 codename_to_api_level_map is needed to translate the codename which may be
699 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700700 """
Doug Zongker951495f2009-08-14 12:44:19 -0700701
Alex Klyubin9667b182015-12-10 13:38:50 -0800702 java_library_path = os.path.join(
703 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
704
Tao Baoe95540e2016-11-08 12:08:53 -0800705 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
706 ["-Djava.library.path=" + java_library_path,
707 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
708 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700709 if whole_file:
710 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800711
712 min_sdk_version = min_api_level
713 if min_sdk_version is None:
714 if not whole_file:
715 min_sdk_version = GetMinSdkVersionInt(
716 input_name, codename_to_api_level_map)
717 if min_sdk_version is not None:
718 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
719
T.R. Fullhart37e10522013-03-18 10:31:26 -0700720 cmd.extend([key + OPTIONS.public_key_suffix,
721 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800722 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700723
724 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700725 if password is not None:
726 password += "\n"
727 p.communicate(password)
728 if p.returncode != 0:
729 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
730
Doug Zongkereef39442009-04-02 12:14:19 -0700731
Doug Zongker37974732010-09-16 17:44:38 -0700732def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700733 """Check the data string passed against the max size limit, if
734 any, for the given target. Raise exception if the data is too big.
735 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700736
Dan Albert8b72aef2015-03-23 19:13:21 -0700737 if target.endswith(".img"):
738 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700739 mount_point = "/" + target
740
Ying Wangf8824af2014-06-03 14:07:27 -0700741 fs_type = None
742 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700743 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700744 if mount_point == "/userdata":
745 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700746 p = info_dict["fstab"][mount_point]
747 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800748 device = p.device
749 if "/" in device:
750 device = device[device.rfind("/")+1:]
751 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700752 if not fs_type or not limit:
753 return
Doug Zongkereef39442009-04-02 12:14:19 -0700754
Andrew Boie0f9aec82012-02-14 09:32:52 -0800755 size = len(data)
756 pct = float(size) * 100.0 / limit
757 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
758 if pct >= 99.0:
759 raise ExternalError(msg)
760 elif pct >= 95.0:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800761 print("\n WARNING: %s\n" % (msg,))
Andrew Boie0f9aec82012-02-14 09:32:52 -0800762 elif OPTIONS.verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800763 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700764
765
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800766def ReadApkCerts(tf_zip):
767 """Given a target_files ZipFile, parse the META/apkcerts.txt file
Narayan Kamatha07bf042017-08-14 14:49:21 +0100768 and return a tuple with the following elements: (1) a dictionary that maps
769 packages to certs (based on the "certificate" and "private_key" attributes
770 in the file. (2) A string representing the extension of compressed APKs in
771 the target files (e.g ".gz" ".bro")."""
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800772 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100773 compressed_extension = None
774
Tao Bao0f990332017-09-08 19:02:54 -0700775 # META/apkcerts.txt contains the info for _all_ the packages known at build
776 # time. Filter out the ones that are not installed.
777 installed_files = set()
778 for name in tf_zip.namelist():
779 basename = os.path.basename(name)
780 if basename:
781 installed_files.add(basename)
782
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800783 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
784 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700785 if not line:
786 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100787 m = re.match(r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
788 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
789 line)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800790 if m:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100791 matches = m.groupdict()
792 cert = matches["CERT"]
793 privkey = matches["PRIVKEY"]
794 name = matches["NAME"]
795 this_compressed_extension = matches["COMPRESSED"]
T.R. Fullhart37e10522013-03-18 10:31:26 -0700796 public_key_suffix_len = len(OPTIONS.public_key_suffix)
797 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800798 if cert in SPECIAL_CERT_STRINGS and not privkey:
799 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700800 elif (cert.endswith(OPTIONS.public_key_suffix) and
801 privkey.endswith(OPTIONS.private_key_suffix) and
802 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
803 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800804 else:
805 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
Narayan Kamatha07bf042017-08-14 14:49:21 +0100806 if this_compressed_extension:
Tao Bao0f990332017-09-08 19:02:54 -0700807 # Only count the installed files.
808 filename = name + '.' + this_compressed_extension
809 if filename not in installed_files:
810 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100811 # Make sure that all the values in the compression map have the same
812 # extension. We don't support multiple compression methods in the same
813 # system image.
814 if compressed_extension:
815 if this_compressed_extension != compressed_extension:
816 raise ValueError("multiple compressed extensions : %s vs %s",
817 (compressed_extension, this_compressed_extension))
818 else:
819 compressed_extension = this_compressed_extension
820
821 return (certmap, ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800822
823
Doug Zongkereef39442009-04-02 12:14:19 -0700824COMMON_DOCSTRING = """
825 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700826 Prepend <dir>/bin to the list of places to search for binaries
827 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700828
Doug Zongker05d3dea2009-06-22 11:32:31 -0700829 -s (--device_specific) <file>
830 Path to the python module containing device-specific
831 releasetools code.
832
Doug Zongker8bec09e2009-11-30 15:37:14 -0800833 -x (--extra) <key=value>
834 Add a key/value pair to the 'extras' dict, which device-specific
835 extension code may look at.
836
Doug Zongkereef39442009-04-02 12:14:19 -0700837 -v (--verbose)
838 Show command lines being executed.
839
840 -h (--help)
841 Display this usage message and exit.
842"""
843
844def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800845 print(docstring.rstrip("\n"))
846 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700847
848
849def ParseOptions(argv,
850 docstring,
851 extra_opts="", extra_long_opts=(),
852 extra_option_handler=None):
853 """Parse the options in argv and return any arguments that aren't
854 flags. docstring is the calling module's docstring, to be displayed
855 for errors and -h. extra_opts and extra_long_opts are for flags
856 defined by the caller, which are processed by passing them to
857 extra_option_handler."""
858
859 try:
860 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800861 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800862 ["help", "verbose", "path=", "signapk_path=",
863 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700864 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700865 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
866 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800867 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700868 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700869 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700870 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800871 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700872 sys.exit(2)
873
Doug Zongkereef39442009-04-02 12:14:19 -0700874 for o, a in opts:
875 if o in ("-h", "--help"):
876 Usage(docstring)
877 sys.exit()
878 elif o in ("-v", "--verbose"):
879 OPTIONS.verbose = True
880 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700881 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700882 elif o in ("--signapk_path",):
883 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800884 elif o in ("--signapk_shared_library_path",):
885 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700886 elif o in ("--extra_signapk_args",):
887 OPTIONS.extra_signapk_args = shlex.split(a)
888 elif o in ("--java_path",):
889 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700890 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800891 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700892 elif o in ("--public_key_suffix",):
893 OPTIONS.public_key_suffix = a
894 elif o in ("--private_key_suffix",):
895 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800896 elif o in ("--boot_signer_path",):
897 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700898 elif o in ("--boot_signer_args",):
899 OPTIONS.boot_signer_args = shlex.split(a)
900 elif o in ("--verity_signer_path",):
901 OPTIONS.verity_signer_path = a
902 elif o in ("--verity_signer_args",):
903 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700904 elif o in ("-s", "--device_specific"):
905 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800906 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800907 key, value = a.split("=", 1)
908 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700909 else:
910 if extra_option_handler is None or not extra_option_handler(o, a):
911 assert False, "unknown option \"%s\"" % (o,)
912
Doug Zongker85448772014-09-09 14:59:20 -0700913 if OPTIONS.search_path:
914 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
915 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700916
917 return args
918
919
Tao Bao4c851b12016-09-19 13:54:38 -0700920def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700921 """Make a temp file and add it to the list of things to be deleted
922 when Cleanup() is called. Return the filename."""
923 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
924 os.close(fd)
925 OPTIONS.tempfiles.append(fn)
926 return fn
927
928
Doug Zongkereef39442009-04-02 12:14:19 -0700929def Cleanup():
930 for i in OPTIONS.tempfiles:
931 if os.path.isdir(i):
932 shutil.rmtree(i)
933 else:
934 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700935
936
937class PasswordManager(object):
938 def __init__(self):
939 self.editor = os.getenv("EDITOR", None)
940 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
941
942 def GetPasswords(self, items):
943 """Get passwords corresponding to each string in 'items',
944 returning a dict. (The dict may have keys in addition to the
945 values in 'items'.)
946
947 Uses the passwords in $ANDROID_PW_FILE if available, letting the
948 user edit that file to add more needed passwords. If no editor is
949 available, or $ANDROID_PW_FILE isn't define, prompts the user
950 interactively in the ordinary way.
951 """
952
953 current = self.ReadFile()
954
955 first = True
956 while True:
957 missing = []
958 for i in items:
959 if i not in current or not current[i]:
960 missing.append(i)
961 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700962 if not missing:
963 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700964
965 for i in missing:
966 current[i] = ""
967
968 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800969 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700970 answer = raw_input("try to edit again? [y]> ").strip()
971 if answer and answer[0] not in 'yY':
972 raise RuntimeError("key passwords unavailable")
973 first = False
974
975 current = self.UpdateAndReadFile(current)
976
Dan Albert8b72aef2015-03-23 19:13:21 -0700977 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700978 """Prompt the user to enter a value (password) for each key in
979 'current' whose value is fales. Returns a new dict with all the
980 values.
981 """
982 result = {}
983 for k, v in sorted(current.iteritems()):
984 if v:
985 result[k] = v
986 else:
987 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700988 result[k] = getpass.getpass(
989 "Enter password for %s key> " % k).strip()
990 if result[k]:
991 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700992 return result
993
994 def UpdateAndReadFile(self, current):
995 if not self.editor or not self.pwfile:
996 return self.PromptResult(current)
997
998 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700999 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001000 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1001 f.write("# (Additional spaces are harmless.)\n\n")
1002
1003 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001004 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1005 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001006 f.write("[[[ %s ]]] %s\n" % (v, k))
1007 if not v and first_line is None:
1008 # position cursor on first line with no password.
1009 first_line = i + 4
1010 f.close()
1011
1012 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1013 _, _ = p.communicate()
1014
1015 return self.ReadFile()
1016
1017 def ReadFile(self):
1018 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001019 if self.pwfile is None:
1020 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001021 try:
1022 f = open(self.pwfile, "r")
1023 for line in f:
1024 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001025 if not line or line[0] == '#':
1026 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001027 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1028 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001029 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001030 else:
1031 result[m.group(2)] = m.group(1)
1032 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001033 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001034 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001035 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001036 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001037
1038
Dan Albert8e0178d2015-01-27 15:53:15 -08001039def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1040 compress_type=None):
1041 import datetime
1042
1043 # http://b/18015246
1044 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1045 # for files larger than 2GiB. We can work around this by adjusting their
1046 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1047 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1048 # it isn't clear to me exactly what circumstances cause this).
1049 # `zipfile.write()` must be used directly to work around this.
1050 #
1051 # This mess can be avoided if we port to python3.
1052 saved_zip64_limit = zipfile.ZIP64_LIMIT
1053 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1054
1055 if compress_type is None:
1056 compress_type = zip_file.compression
1057 if arcname is None:
1058 arcname = filename
1059
1060 saved_stat = os.stat(filename)
1061
1062 try:
1063 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1064 # file to be zipped and reset it when we're done.
1065 os.chmod(filename, perms)
1066
1067 # Use a fixed timestamp so the output is repeatable.
1068 epoch = datetime.datetime.fromtimestamp(0)
1069 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1070 os.utime(filename, (timestamp, timestamp))
1071
1072 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1073 finally:
1074 os.chmod(filename, saved_stat.st_mode)
1075 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1076 zipfile.ZIP64_LIMIT = saved_zip64_limit
1077
1078
Tao Bao58c1b962015-05-20 09:32:18 -07001079def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001080 compress_type=None):
1081 """Wrap zipfile.writestr() function to work around the zip64 limit.
1082
1083 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1084 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1085 when calling crc32(bytes).
1086
1087 But it still works fine to write a shorter string into a large zip file.
1088 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1089 when we know the string won't be too long.
1090 """
1091
1092 saved_zip64_limit = zipfile.ZIP64_LIMIT
1093 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1094
1095 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1096 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001097 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001098 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001099 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001100 else:
Tao Baof3282b42015-04-01 11:21:55 -07001101 zinfo = zinfo_or_arcname
1102
1103 # If compress_type is given, it overrides the value in zinfo.
1104 if compress_type is not None:
1105 zinfo.compress_type = compress_type
1106
Tao Bao58c1b962015-05-20 09:32:18 -07001107 # If perms is given, it has a priority.
1108 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001109 # If perms doesn't set the file type, mark it as a regular file.
1110 if perms & 0o770000 == 0:
1111 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001112 zinfo.external_attr = perms << 16
1113
Tao Baof3282b42015-04-01 11:21:55 -07001114 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001115 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1116
Dan Albert8b72aef2015-03-23 19:13:21 -07001117 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001118 zipfile.ZIP64_LIMIT = saved_zip64_limit
1119
1120
1121def ZipClose(zip_file):
1122 # http://b/18015246
1123 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1124 # central directory.
1125 saved_zip64_limit = zipfile.ZIP64_LIMIT
1126 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1127
1128 zip_file.close()
1129
1130 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001131
1132
1133class DeviceSpecificParams(object):
1134 module = None
1135 def __init__(self, **kwargs):
1136 """Keyword arguments to the constructor become attributes of this
1137 object, which is passed to all functions in the device-specific
1138 module."""
1139 for k, v in kwargs.iteritems():
1140 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001141 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001142
1143 if self.module is None:
1144 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001145 if not path:
1146 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001147 try:
1148 if os.path.isdir(path):
1149 info = imp.find_module("releasetools", [path])
1150 else:
1151 d, f = os.path.split(path)
1152 b, x = os.path.splitext(f)
1153 if x == ".py":
1154 f = b
1155 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001156 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001157 self.module = imp.load_module("device_specific", *info)
1158 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001159 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001160
1161 def _DoCall(self, function_name, *args, **kwargs):
1162 """Call the named function in the device-specific module, passing
1163 the given args and kwargs. The first argument to the call will be
1164 the DeviceSpecific object itself. If there is no module, or the
1165 module does not define the function, return the value of the
1166 'default' kwarg (which itself defaults to None)."""
1167 if self.module is None or not hasattr(self.module, function_name):
1168 return kwargs.get("default", None)
1169 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1170
1171 def FullOTA_Assertions(self):
1172 """Called after emitting the block of assertions at the top of a
1173 full OTA package. Implementations can add whatever additional
1174 assertions they like."""
1175 return self._DoCall("FullOTA_Assertions")
1176
Doug Zongkere5ff5902012-01-17 10:55:37 -08001177 def FullOTA_InstallBegin(self):
1178 """Called at the start of full OTA installation."""
1179 return self._DoCall("FullOTA_InstallBegin")
1180
Doug Zongker05d3dea2009-06-22 11:32:31 -07001181 def FullOTA_InstallEnd(self):
1182 """Called at the end of full OTA installation; typically this is
1183 used to install the image for the device's baseband processor."""
1184 return self._DoCall("FullOTA_InstallEnd")
1185
1186 def IncrementalOTA_Assertions(self):
1187 """Called after emitting the block of assertions at the top of an
1188 incremental OTA package. Implementations can add whatever
1189 additional assertions they like."""
1190 return self._DoCall("IncrementalOTA_Assertions")
1191
Doug Zongkere5ff5902012-01-17 10:55:37 -08001192 def IncrementalOTA_VerifyBegin(self):
1193 """Called at the start of the verification phase of incremental
1194 OTA installation; additional checks can be placed here to abort
1195 the script before any changes are made."""
1196 return self._DoCall("IncrementalOTA_VerifyBegin")
1197
Doug Zongker05d3dea2009-06-22 11:32:31 -07001198 def IncrementalOTA_VerifyEnd(self):
1199 """Called at the end of the verification phase of incremental OTA
1200 installation; additional checks can be placed here to abort the
1201 script before any changes are made."""
1202 return self._DoCall("IncrementalOTA_VerifyEnd")
1203
Doug Zongkere5ff5902012-01-17 10:55:37 -08001204 def IncrementalOTA_InstallBegin(self):
1205 """Called at the start of incremental OTA installation (after
1206 verification is complete)."""
1207 return self._DoCall("IncrementalOTA_InstallBegin")
1208
Doug Zongker05d3dea2009-06-22 11:32:31 -07001209 def IncrementalOTA_InstallEnd(self):
1210 """Called at the end of incremental OTA installation; typically
1211 this is used to install the image for the device's baseband
1212 processor."""
1213 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001214
Tao Bao9bc6bb22015-11-09 16:58:28 -08001215 def VerifyOTA_Assertions(self):
1216 return self._DoCall("VerifyOTA_Assertions")
1217
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001218class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001219 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001220 self.name = name
1221 self.data = data
1222 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001223 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001224 self.sha1 = sha1(data).hexdigest()
1225
1226 @classmethod
1227 def FromLocalFile(cls, name, diskname):
1228 f = open(diskname, "rb")
1229 data = f.read()
1230 f.close()
1231 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001232
1233 def WriteToTemp(self):
1234 t = tempfile.NamedTemporaryFile()
1235 t.write(self.data)
1236 t.flush()
1237 return t
1238
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001239 def WriteToDir(self, d):
1240 with open(os.path.join(d, self.name), "wb") as fp:
1241 fp.write(self.data)
1242
Geremy Condra36bd3652014-02-06 19:45:10 -08001243 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001244 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001245
1246DIFF_PROGRAM_BY_EXT = {
1247 ".gz" : "imgdiff",
1248 ".zip" : ["imgdiff", "-z"],
1249 ".jar" : ["imgdiff", "-z"],
1250 ".apk" : ["imgdiff", "-z"],
1251 ".img" : "imgdiff",
1252 }
1253
1254class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001255 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001256 self.tf = tf
1257 self.sf = sf
1258 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001259 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001260
1261 def ComputePatch(self):
1262 """Compute the patch (as a string of data) needed to turn sf into
1263 tf. Returns the same tuple as GetPatch()."""
1264
1265 tf = self.tf
1266 sf = self.sf
1267
Doug Zongker24cd2802012-08-14 16:36:15 -07001268 if self.diff_program:
1269 diff_program = self.diff_program
1270 else:
1271 ext = os.path.splitext(tf.name)[1]
1272 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001273
1274 ttemp = tf.WriteToTemp()
1275 stemp = sf.WriteToTemp()
1276
1277 ext = os.path.splitext(tf.name)[1]
1278
1279 try:
1280 ptemp = tempfile.NamedTemporaryFile()
1281 if isinstance(diff_program, list):
1282 cmd = copy.copy(diff_program)
1283 else:
1284 cmd = [diff_program]
1285 cmd.append(stemp.name)
1286 cmd.append(ttemp.name)
1287 cmd.append(ptemp.name)
1288 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001289 err = []
1290 def run():
1291 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001292 if e:
1293 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001294 th = threading.Thread(target=run)
1295 th.start()
1296 th.join(timeout=300) # 5 mins
1297 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001298 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001299 p.terminate()
1300 th.join(5)
1301 if th.is_alive():
1302 p.kill()
1303 th.join()
1304
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001305 if err or p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001306 print("WARNING: failure running %s:\n%s\n" % (
1307 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001308 self.patch = None
1309 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001310 diff = ptemp.read()
1311 finally:
1312 ptemp.close()
1313 stemp.close()
1314 ttemp.close()
1315
1316 self.patch = diff
1317 return self.tf, self.sf, self.patch
1318
1319
1320 def GetPatch(self):
1321 """Return a tuple (target_file, source_file, patch_data).
1322 patch_data may be None if ComputePatch hasn't been called, or if
1323 computing the patch failed."""
1324 return self.tf, self.sf, self.patch
1325
1326
1327def ComputeDifferences(diffs):
1328 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001329 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001330
1331 # Do the largest files first, to try and reduce the long-pole effect.
1332 by_size = [(i.tf.size, i) for i in diffs]
1333 by_size.sort(reverse=True)
1334 by_size = [i[1] for i in by_size]
1335
1336 lock = threading.Lock()
1337 diff_iter = iter(by_size) # accessed under lock
1338
1339 def worker():
1340 try:
1341 lock.acquire()
1342 for d in diff_iter:
1343 lock.release()
1344 start = time.time()
1345 d.ComputePatch()
1346 dur = time.time() - start
1347 lock.acquire()
1348
1349 tf, sf, patch = d.GetPatch()
1350 if sf.name == tf.name:
1351 name = tf.name
1352 else:
1353 name = "%s (%s)" % (tf.name, sf.name)
1354 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001355 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001356 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001357 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1358 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001359 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001360 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001361 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001362 raise
1363
1364 # start worker threads; wait for them all to finish.
1365 threads = [threading.Thread(target=worker)
1366 for i in range(OPTIONS.worker_threads)]
1367 for th in threads:
1368 th.start()
1369 while threads:
1370 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001371
1372
Dan Albert8b72aef2015-03-23 19:13:21 -07001373class BlockDifference(object):
1374 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001375 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001376 self.tgt = tgt
1377 self.src = src
1378 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001379 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001380 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001381
Tao Baodd2a5892015-03-12 12:32:37 -07001382 if version is None:
1383 version = 1
1384 if OPTIONS.info_dict:
1385 version = max(
1386 int(i) for i in
1387 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001388 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001389 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001390
1391 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001392 version=self.version,
1393 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001394 tmpdir = tempfile.mkdtemp()
1395 OPTIONS.tempfiles.append(tmpdir)
1396 self.path = os.path.join(tmpdir, partition)
1397 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001398 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001399 self.touched_src_ranges = b.touched_src_ranges
1400 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001401
Tao Baoaac4ad52015-10-16 15:26:34 -07001402 if src is None:
1403 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1404 else:
1405 _, self.device = GetTypeAndDevice("/" + partition,
1406 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001407
Tao Baod8d14be2016-02-04 14:26:02 -08001408 @property
1409 def required_cache(self):
1410 return self._required_cache
1411
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001412 def WriteScript(self, script, output_zip, progress=None):
1413 if not self.src:
1414 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001415 script.Print("Patching %s image unconditionally..." % (self.partition,))
1416 else:
1417 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001418
Dan Albert8b72aef2015-03-23 19:13:21 -07001419 if progress:
1420 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001421 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001422 if OPTIONS.verify:
1423 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001424
Tao Bao9bc6bb22015-11-09 16:58:28 -08001425 def WriteStrictVerifyScript(self, script):
1426 """Verify all the blocks in the care_map, including clobbered blocks.
1427
1428 This differs from the WriteVerifyScript() function: a) it prints different
1429 error messages; b) it doesn't allow half-way updated images to pass the
1430 verification."""
1431
1432 partition = self.partition
1433 script.Print("Verifying %s..." % (partition,))
1434 ranges = self.tgt.care_map
1435 ranges_str = ranges.to_string_raw()
1436 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1437 'ui_print(" Verified.") || '
1438 'ui_print("\\"%s\\" has unexpected contents.");' % (
1439 self.device, ranges_str,
1440 self.tgt.TotalSha1(include_clobbered_blocks=True),
1441 self.device))
1442 script.AppendExtra("")
1443
Tao Baod522bdc2016-04-12 15:53:16 -07001444 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001445 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001446
1447 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001448 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001449 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001450
1451 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001452 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001453 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001454 ranges = self.touched_src_ranges
1455 expected_sha1 = self.touched_src_sha1
1456 else:
1457 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1458 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001459
1460 # No blocks to be checked, skipping.
1461 if not ranges:
1462 return
1463
Tao Bao5ece99d2015-05-12 11:42:31 -07001464 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001465 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1466 'block_image_verify("%s", '
1467 'package_extract_file("%s.transfer.list"), '
1468 '"%s.new.dat", "%s.patch.dat")) then') % (
1469 self.device, ranges_str, expected_sha1,
1470 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001471 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001472 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001473
Tianjie Xufc3422a2015-12-15 11:53:59 -08001474 if self.version >= 4:
1475
1476 # Bug: 21124327
1477 # When generating incrementals for the system and vendor partitions in
1478 # version 4 or newer, explicitly check the first block (which contains
1479 # the superblock) of the partition to see if it's what we expect. If
1480 # this check fails, give an explicit log message about the partition
1481 # having been remounted R/W (the most likely explanation).
1482 if self.check_first_block:
1483 script.AppendExtra('check_first_block("%s");' % (self.device,))
1484
1485 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001486 if partition == "system":
1487 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1488 else:
1489 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001490 script.AppendExtra((
1491 'ifelse (block_image_recover("{device}", "{ranges}") && '
1492 'block_image_verify("{device}", '
1493 'package_extract_file("{partition}.transfer.list"), '
1494 '"{partition}.new.dat", "{partition}.patch.dat"), '
1495 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001496 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001497 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001498 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001499
Tao Baodd2a5892015-03-12 12:32:37 -07001500 # Abort the OTA update. Note that the incremental OTA cannot be applied
1501 # even if it may match the checksum of the target partition.
1502 # a) If version < 3, operations like move and erase will make changes
1503 # unconditionally and damage the partition.
1504 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001505 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001506 if partition == "system":
1507 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1508 else:
1509 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1510 script.AppendExtra((
1511 'abort("E%d: %s partition has unexpected contents");\n'
1512 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001513
Tao Bao5fcaaef2015-06-01 13:40:49 -07001514 def _WritePostInstallVerifyScript(self, script):
1515 partition = self.partition
1516 script.Print('Verifying the updated %s image...' % (partition,))
1517 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1518 ranges = self.tgt.care_map
1519 ranges_str = ranges.to_string_raw()
1520 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1521 self.device, ranges_str,
1522 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001523
1524 # Bug: 20881595
1525 # Verify that extended blocks are really zeroed out.
1526 if self.tgt.extended:
1527 ranges_str = self.tgt.extended.to_string_raw()
1528 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1529 self.device, ranges_str,
1530 self._HashZeroBlocks(self.tgt.extended.size())))
1531 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001532 if partition == "system":
1533 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1534 else:
1535 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001536 script.AppendExtra(
1537 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001538 ' abort("E%d: %s partition has unexpected non-zero contents after '
1539 'OTA update");\n'
1540 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001541 else:
1542 script.Print('Verified the updated %s image.' % (partition,))
1543
Tianjie Xu209db462016-05-24 17:34:52 -07001544 if partition == "system":
1545 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1546 else:
1547 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1548
Tao Bao5fcaaef2015-06-01 13:40:49 -07001549 script.AppendExtra(
1550 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001551 ' abort("E%d: %s partition has unexpected contents after OTA '
1552 'update");\n'
1553 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001554
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001555 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001556 ZipWrite(output_zip,
1557 '{}.transfer.list'.format(self.path),
1558 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001559
1560 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1561 # almost triples the compression time but doesn't further reduce the size too much.
1562 # For a typical 1.8G system.new.dat
1563 # zip | brotli(quality 6) | brotli(quality 9)
1564 # compressed_size: 942M | 869M (~8% reduced) | 854M
1565 # compression_time: 75s | 265s | 719s
1566 # decompression_time: 15s | 25s | 25s
1567
1568 if not self.src:
1569 bro_cmd = ['bro', '--quality', '6',
1570 '--input', '{}.new.dat'.format(self.path),
1571 '--output', '{}.new.dat.br'.format(self.path)]
1572 print("Compressing {}.new.dat with brotli".format(self.partition))
1573 p = Run(bro_cmd, stdout=subprocess.PIPE)
1574 p.communicate()
1575 assert p.returncode == 0,\
1576 'compression of {}.new.dat failed'.format(self.partition)
1577
1578 new_data_name = '{}.new.dat.br'.format(self.partition)
1579 ZipWrite(output_zip,
1580 '{}.new.dat.br'.format(self.path),
1581 new_data_name,
1582 compress_type=zipfile.ZIP_STORED)
1583 else:
1584 new_data_name = '{}.new.dat'.format(self.partition)
1585 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1586
Dan Albert8e0178d2015-01-27 15:53:15 -08001587 ZipWrite(output_zip,
1588 '{}.patch.dat'.format(self.path),
1589 '{}.patch.dat'.format(self.partition),
1590 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001591
Tianjie Xu209db462016-05-24 17:34:52 -07001592 if self.partition == "system":
1593 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1594 else:
1595 code = ErrorCode.VENDOR_UPDATE_FAILURE
1596
Dan Albert8e0178d2015-01-27 15:53:15 -08001597 call = ('block_image_update("{device}", '
1598 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001599 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001600 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001601 device=self.device, partition=self.partition,
1602 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001603 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001604
Dan Albert8b72aef2015-03-23 19:13:21 -07001605 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001606 data = source.ReadRangeSet(ranges)
1607 ctx = sha1()
1608
1609 for p in data:
1610 ctx.update(p)
1611
1612 return ctx.hexdigest()
1613
Tao Baoe9b61912015-07-09 17:37:49 -07001614 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1615 """Return the hash value for all zero blocks."""
1616 zero_block = '\x00' * 4096
1617 ctx = sha1()
1618 for _ in range(num_blocks):
1619 ctx.update(zero_block)
1620
1621 return ctx.hexdigest()
1622
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001623
1624DataImage = blockimgdiff.DataImage
1625
Doug Zongker96a57e72010-09-26 14:57:41 -07001626# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001627PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001628 "ext4": "EMMC",
1629 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001630 "f2fs": "EMMC",
1631 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001632}
Doug Zongker96a57e72010-09-26 14:57:41 -07001633
1634def GetTypeAndDevice(mount_point, info):
1635 fstab = info["fstab"]
1636 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001637 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1638 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001639 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001640 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001641
1642
1643def ParseCertificate(data):
1644 """Parse a PEM-format certificate."""
1645 cert = []
1646 save = False
1647 for line in data.split("\n"):
1648 if "--END CERTIFICATE--" in line:
1649 break
1650 if save:
1651 cert.append(line)
1652 if "--BEGIN CERTIFICATE--" in line:
1653 save = True
1654 cert = "".join(cert).decode('base64')
1655 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001656
Doug Zongker412c02f2014-02-13 10:58:24 -08001657def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1658 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001659 """Generate a binary patch that creates the recovery image starting
1660 with the boot image. (Most of the space in these images is just the
1661 kernel, which is identical for the two, so the resulting patch
1662 should be efficient.) Add it to the output zip, along with a shell
1663 script that is run from init.rc on first boot to actually do the
1664 patching and install the new recovery image.
1665
1666 recovery_img and boot_img should be File objects for the
1667 corresponding images. info should be the dictionary returned by
1668 common.LoadInfoDict() on the input target_files.
1669 """
1670
Doug Zongker412c02f2014-02-13 10:58:24 -08001671 if info_dict is None:
1672 info_dict = OPTIONS.info_dict
1673
Tao Baof2cffbd2015-07-22 12:33:18 -07001674 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001675
Tao Baof2cffbd2015-07-22 12:33:18 -07001676 if full_recovery_image:
1677 output_sink("etc/recovery.img", recovery_img.data)
1678
1679 else:
1680 diff_program = ["imgdiff"]
1681 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1682 if os.path.exists(path):
1683 diff_program.append("-b")
1684 diff_program.append(path)
1685 bonus_args = "-b /system/etc/recovery-resource.dat"
1686 else:
1687 bonus_args = ""
1688
1689 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1690 _, _, patch = d.ComputePatch()
1691 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001692
Dan Albertebb19aa2015-03-27 19:11:53 -07001693 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001694 # The following GetTypeAndDevice()s need to use the path in the target
1695 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001696 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1697 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1698 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001699 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001700
Tao Baof2cffbd2015-07-22 12:33:18 -07001701 if full_recovery_image:
1702 sh = """#!/system/bin/sh
1703if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1704 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"
1705else
1706 log -t recovery "Recovery image already installed"
1707fi
1708""" % {'type': recovery_type,
1709 'device': recovery_device,
1710 'sha1': recovery_img.sha1,
1711 'size': recovery_img.size}
1712 else:
1713 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001714if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1715 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"
1716else
1717 log -t recovery "Recovery image already installed"
1718fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001719""" % {'boot_size': boot_img.size,
1720 'boot_sha1': boot_img.sha1,
1721 'recovery_size': recovery_img.size,
1722 'recovery_sha1': recovery_img.sha1,
1723 'boot_type': boot_type,
1724 'boot_device': boot_device,
1725 'recovery_type': recovery_type,
1726 'recovery_device': recovery_device,
1727 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001728
1729 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001730 # in the L release.
1731 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001732
Tao Bao89fbb0f2017-01-10 10:47:58 -08001733 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001734
1735 output_sink(sh_location, sh)