blob: f3096c924b92860c7d3a24a4da7f0a469c5fb3e9 [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])
Tao Bao16cda2f2017-09-27 17:17:43 -0700356 avb_salt = OPTIONS.info_dict.get("avb_salt")
357 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
358 if avb_salt and partition != "vbmeta":
359 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800360
361
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700362def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800363 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700364 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700365
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700366 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800367 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
368 we are building a two-step special image (i.e. building a recovery image to
369 be loaded into /boot in two-step OTAs).
370
371 Return the image data, or None if sourcedir does not appear to contains files
372 for building the requested image.
373 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700374
375 def make_ramdisk():
376 ramdisk_img = tempfile.NamedTemporaryFile()
377
378 if os.access(fs_config_file, os.F_OK):
379 cmd = ["mkbootfs", "-f", fs_config_file,
380 os.path.join(sourcedir, "RAMDISK")]
381 else:
382 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
383 p1 = Run(cmd, stdout=subprocess.PIPE)
384 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
385
386 p2.wait()
387 p1.wait()
388 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
389 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
390
391 return ramdisk_img
392
393 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
394 return None
395
396 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700397 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700398
Doug Zongkerd5131602012-08-02 14:46:42 -0700399 if info_dict is None:
400 info_dict = OPTIONS.info_dict
401
Doug Zongkereef39442009-04-02 12:14:19 -0700402 img = tempfile.NamedTemporaryFile()
403
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700404 if has_ramdisk:
405 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700406
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800407 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
408 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
409
410 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700411
Benoit Fradina45a8682014-07-14 21:00:43 +0200412 fn = os.path.join(sourcedir, "second")
413 if os.access(fn, os.F_OK):
414 cmd.append("--second")
415 cmd.append(fn)
416
Doug Zongker171f1cd2009-06-15 22:36:37 -0700417 fn = os.path.join(sourcedir, "cmdline")
418 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700419 cmd.append("--cmdline")
420 cmd.append(open(fn).read().rstrip("\n"))
421
422 fn = os.path.join(sourcedir, "base")
423 if os.access(fn, os.F_OK):
424 cmd.append("--base")
425 cmd.append(open(fn).read().rstrip("\n"))
426
Ying Wang4de6b5b2010-08-25 14:29:34 -0700427 fn = os.path.join(sourcedir, "pagesize")
428 if os.access(fn, os.F_OK):
429 cmd.append("--pagesize")
430 cmd.append(open(fn).read().rstrip("\n"))
431
Doug Zongkerd5131602012-08-02 14:46:42 -0700432 args = info_dict.get("mkbootimg_args", None)
433 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700434 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700435
Sami Tolvanen3303d902016-03-15 16:49:30 +0000436 args = info_dict.get("mkbootimg_version_args", None)
437 if args and args.strip():
438 cmd.extend(shlex.split(args))
439
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700440 if has_ramdisk:
441 cmd.extend(["--ramdisk", ramdisk_img.name])
442
Tao Baod95e9fd2015-03-29 23:07:41 -0700443 img_unsigned = None
444 if info_dict.get("vboot", None):
445 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700446 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700447 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700448 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700449
450 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700451 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700452 assert p.returncode == 0, "mkbootimg of %s image failed" % (
453 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700454
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100455 if (info_dict.get("boot_signer", None) == "true" and
456 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800457 # Hard-code the path as "/boot" for two-step special recovery image (which
458 # will be loaded into /boot during the two-step OTA).
459 if two_step_image:
460 path = "/boot"
461 else:
462 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700463 cmd = [OPTIONS.boot_signer_path]
464 cmd.extend(OPTIONS.boot_signer_args)
465 cmd.extend([path, img.name,
466 info_dict["verity_key"] + ".pk8",
467 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700468 p = Run(cmd, stdout=subprocess.PIPE)
469 p.communicate()
470 assert p.returncode == 0, "boot_signer of %s image failed" % path
471
Tao Baod95e9fd2015-03-29 23:07:41 -0700472 # Sign the image if vboot is non-empty.
473 elif info_dict.get("vboot", None):
474 path = "/" + os.path.basename(sourcedir).lower()
475 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800476 # We have switched from the prebuilt futility binary to using the tool
477 # (futility-host) built from the source. Override the setting in the old
478 # TF.zip.
479 futility = info_dict["futility"]
480 if futility.startswith("prebuilts/"):
481 futility = "futility-host"
482 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700483 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700484 info_dict["vboot_key"] + ".vbprivk",
485 info_dict["vboot_subkey"] + ".vbprivk",
486 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700487 img.name]
488 p = Run(cmd, stdout=subprocess.PIPE)
489 p.communicate()
490 assert p.returncode == 0, "vboot_signer of %s image failed" % path
491
Tao Baof3282b42015-04-01 11:21:55 -0700492 # Clean up the temp files.
493 img_unsigned.close()
494 img_keyblock.close()
495
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400496 # AVB: if enabled, calculate and add hash to boot.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800497 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700498 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
499 part_size = info_dict["boot_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400500 cmd = [avbtool, "add_hash_footer", "--image", img.name,
501 "--partition_size", str(part_size), "--partition_name", "boot"]
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800502 AppendAVBSigningArgs(cmd, "boot")
503 args = info_dict.get("avb_boot_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400504 if args and args.strip():
505 cmd.extend(shlex.split(args))
506 p = Run(cmd, stdout=subprocess.PIPE)
507 p.communicate()
508 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
509 os.path.basename(OPTIONS.input_tmp))
David Zeuthend995f4b2016-01-29 16:59:17 -0500510
511 img.seek(os.SEEK_SET, 0)
512 data = img.read()
513
514 if has_ramdisk:
515 ramdisk_img.close()
516 img.close()
517
518 return data
519
520
Doug Zongkerd5131602012-08-02 14:46:42 -0700521def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800522 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700523 """Return a File object with the desired bootable image.
524
525 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
526 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
527 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700528
Doug Zongker55d93282011-01-25 17:03:34 -0800529 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
530 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800531 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800532 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700533
534 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
535 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800536 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700537 return File.FromLocalFile(name, prebuilt_path)
538
Tao Bao89fbb0f2017-01-10 10:47:58 -0800539 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700540
541 if info_dict is None:
542 info_dict = OPTIONS.info_dict
543
544 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800545 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
546 # for recovery.
547 has_ramdisk = (info_dict.get("system_root_image") != "true" or
548 prebuilt_name != "boot.img" or
549 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700550
Doug Zongker6f1d0312014-08-22 08:07:12 -0700551 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400552 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
553 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800554 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700555 if data:
556 return File(name, data)
557 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800558
Doug Zongkereef39442009-04-02 12:14:19 -0700559
Narayan Kamatha07bf042017-08-14 14:49:21 +0100560def Gunzip(in_filename, out_filename):
561 """Gunzip the given gzip compressed file to a given output file.
562 """
563 with gzip.open(in_filename, "rb") as in_file, open(out_filename, "wb") as out_file:
564 shutil.copyfileobj(in_file, out_file)
565
566
Doug Zongker75f17362009-12-08 13:46:44 -0800567def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800568 """Unzip the given archive into a temporary directory and return the name.
569
570 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
571 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
572
573 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
574 main file), open for reading.
575 """
Doug Zongkereef39442009-04-02 12:14:19 -0700576
577 tmp = tempfile.mkdtemp(prefix="targetfiles-")
578 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800579
580 def unzip_to_dir(filename, dirname):
581 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
582 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800583 cmd.extend(pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800584 p = Run(cmd, stdout=subprocess.PIPE)
585 p.communicate()
586 if p.returncode != 0:
587 raise ExternalError("failed to unzip input target-files \"%s\"" %
588 (filename,))
589
590 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
591 if m:
592 unzip_to_dir(m.group(1), tmp)
593 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
594 filename = m.group(1)
595 else:
596 unzip_to_dir(filename, tmp)
597
598 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700599
600
601def GetKeyPasswords(keylist):
602 """Given a list of keys, prompt the user to enter passwords for
603 those which require them. Return a {key: password} dict. password
604 will be None if the key has no password."""
605
Doug Zongker8ce7c252009-05-22 13:34:54 -0700606 no_passwords = []
607 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700608 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700609 devnull = open("/dev/null", "w+b")
610 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800611 # We don't need a password for things that aren't really keys.
612 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700613 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700614 continue
615
T.R. Fullhart37e10522013-03-18 10:31:26 -0700616 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700617 "-inform", "DER", "-nocrypt"],
618 stdin=devnull.fileno(),
619 stdout=devnull.fileno(),
620 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700621 p.communicate()
622 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700623 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700624 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700625 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700626 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
627 "-inform", "DER", "-passin", "pass:"],
628 stdin=devnull.fileno(),
629 stdout=devnull.fileno(),
630 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700631 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700632 if p.returncode == 0:
633 # Encrypted key with empty string as password.
634 key_passwords[k] = ''
635 elif stderr.startswith('Error decrypting key'):
636 # Definitely encrypted key.
637 # It would have said "Error reading key" if it didn't parse correctly.
638 need_passwords.append(k)
639 else:
640 # Potentially, a type of key that openssl doesn't understand.
641 # We'll let the routines in signapk.jar handle it.
642 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700643 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700644
T.R. Fullhart37e10522013-03-18 10:31:26 -0700645 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700646 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700647 return key_passwords
648
649
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800650def GetMinSdkVersion(apk_name):
651 """Get the minSdkVersion delared in the APK. This can be both a decimal number
652 (API Level) or a codename.
653 """
654
655 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
656 output, err = p.communicate()
657 if err:
658 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
659 % (p.returncode,))
660
661 for line in output.split("\n"):
662 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
663 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
664 if m:
665 return m.group(1)
666 raise ExternalError("No minSdkVersion returned by aapt")
667
668
669def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
670 """Get the minSdkVersion declared in the APK as a number (API Level). If
671 minSdkVersion is set to a codename, it is translated to a number using the
672 provided map.
673 """
674
675 version = GetMinSdkVersion(apk_name)
676 try:
677 return int(version)
678 except ValueError:
679 # Not a decimal number. Codename?
680 if version in codename_to_api_level_map:
681 return codename_to_api_level_map[version]
682 else:
683 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
684 % (version, codename_to_api_level_map))
685
686
687def SignFile(input_name, output_name, key, password, min_api_level=None,
688 codename_to_api_level_map=dict(),
689 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700690 """Sign the input_name zip/jar/apk, producing output_name. Use the
691 given key and password (the latter may be None if the key does not
692 have a password.
693
Doug Zongker951495f2009-08-14 12:44:19 -0700694 If whole_file is true, use the "-w" option to SignApk to embed a
695 signature that covers the whole file in the archive comment of the
696 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800697
698 min_api_level is the API Level (int) of the oldest platform this file may end
699 up on. If not specified for an APK, the API Level is obtained by interpreting
700 the minSdkVersion attribute of the APK's AndroidManifest.xml.
701
702 codename_to_api_level_map is needed to translate the codename which may be
703 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700704 """
Doug Zongker951495f2009-08-14 12:44:19 -0700705
Alex Klyubin9667b182015-12-10 13:38:50 -0800706 java_library_path = os.path.join(
707 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
708
Tao Baoe95540e2016-11-08 12:08:53 -0800709 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
710 ["-Djava.library.path=" + java_library_path,
711 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
712 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700713 if whole_file:
714 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800715
716 min_sdk_version = min_api_level
717 if min_sdk_version is None:
718 if not whole_file:
719 min_sdk_version = GetMinSdkVersionInt(
720 input_name, codename_to_api_level_map)
721 if min_sdk_version is not None:
722 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
723
T.R. Fullhart37e10522013-03-18 10:31:26 -0700724 cmd.extend([key + OPTIONS.public_key_suffix,
725 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800726 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700727
728 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700729 if password is not None:
730 password += "\n"
731 p.communicate(password)
732 if p.returncode != 0:
733 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
734
Doug Zongkereef39442009-04-02 12:14:19 -0700735
Doug Zongker37974732010-09-16 17:44:38 -0700736def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700737 """Check the data string passed against the max size limit, if
738 any, for the given target. Raise exception if the data is too big.
739 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700740
Dan Albert8b72aef2015-03-23 19:13:21 -0700741 if target.endswith(".img"):
742 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700743 mount_point = "/" + target
744
Ying Wangf8824af2014-06-03 14:07:27 -0700745 fs_type = None
746 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700747 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700748 if mount_point == "/userdata":
749 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700750 p = info_dict["fstab"][mount_point]
751 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800752 device = p.device
753 if "/" in device:
754 device = device[device.rfind("/")+1:]
755 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700756 if not fs_type or not limit:
757 return
Doug Zongkereef39442009-04-02 12:14:19 -0700758
Andrew Boie0f9aec82012-02-14 09:32:52 -0800759 size = len(data)
760 pct = float(size) * 100.0 / limit
761 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
762 if pct >= 99.0:
763 raise ExternalError(msg)
764 elif pct >= 95.0:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800765 print("\n WARNING: %s\n" % (msg,))
Andrew Boie0f9aec82012-02-14 09:32:52 -0800766 elif OPTIONS.verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800767 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700768
769
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800770def ReadApkCerts(tf_zip):
771 """Given a target_files ZipFile, parse the META/apkcerts.txt file
Narayan Kamatha07bf042017-08-14 14:49:21 +0100772 and return a tuple with the following elements: (1) a dictionary that maps
773 packages to certs (based on the "certificate" and "private_key" attributes
774 in the file. (2) A string representing the extension of compressed APKs in
775 the target files (e.g ".gz" ".bro")."""
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800776 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100777 compressed_extension = None
778
Tao Bao0f990332017-09-08 19:02:54 -0700779 # META/apkcerts.txt contains the info for _all_ the packages known at build
780 # time. Filter out the ones that are not installed.
781 installed_files = set()
782 for name in tf_zip.namelist():
783 basename = os.path.basename(name)
784 if basename:
785 installed_files.add(basename)
786
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800787 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
788 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700789 if not line:
790 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100791 m = re.match(r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
792 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
793 line)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800794 if m:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100795 matches = m.groupdict()
796 cert = matches["CERT"]
797 privkey = matches["PRIVKEY"]
798 name = matches["NAME"]
799 this_compressed_extension = matches["COMPRESSED"]
T.R. Fullhart37e10522013-03-18 10:31:26 -0700800 public_key_suffix_len = len(OPTIONS.public_key_suffix)
801 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800802 if cert in SPECIAL_CERT_STRINGS and not privkey:
803 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700804 elif (cert.endswith(OPTIONS.public_key_suffix) and
805 privkey.endswith(OPTIONS.private_key_suffix) and
806 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
807 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800808 else:
809 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
Narayan Kamatha07bf042017-08-14 14:49:21 +0100810 if this_compressed_extension:
Tao Bao0f990332017-09-08 19:02:54 -0700811 # Only count the installed files.
812 filename = name + '.' + this_compressed_extension
813 if filename not in installed_files:
814 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100815 # Make sure that all the values in the compression map have the same
816 # extension. We don't support multiple compression methods in the same
817 # system image.
818 if compressed_extension:
819 if this_compressed_extension != compressed_extension:
820 raise ValueError("multiple compressed extensions : %s vs %s",
821 (compressed_extension, this_compressed_extension))
822 else:
823 compressed_extension = this_compressed_extension
824
825 return (certmap, ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800826
827
Doug Zongkereef39442009-04-02 12:14:19 -0700828COMMON_DOCSTRING = """
829 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700830 Prepend <dir>/bin to the list of places to search for binaries
831 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700832
Doug Zongker05d3dea2009-06-22 11:32:31 -0700833 -s (--device_specific) <file>
834 Path to the python module containing device-specific
835 releasetools code.
836
Doug Zongker8bec09e2009-11-30 15:37:14 -0800837 -x (--extra) <key=value>
838 Add a key/value pair to the 'extras' dict, which device-specific
839 extension code may look at.
840
Doug Zongkereef39442009-04-02 12:14:19 -0700841 -v (--verbose)
842 Show command lines being executed.
843
844 -h (--help)
845 Display this usage message and exit.
846"""
847
848def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800849 print(docstring.rstrip("\n"))
850 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700851
852
853def ParseOptions(argv,
854 docstring,
855 extra_opts="", extra_long_opts=(),
856 extra_option_handler=None):
857 """Parse the options in argv and return any arguments that aren't
858 flags. docstring is the calling module's docstring, to be displayed
859 for errors and -h. extra_opts and extra_long_opts are for flags
860 defined by the caller, which are processed by passing them to
861 extra_option_handler."""
862
863 try:
864 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800865 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800866 ["help", "verbose", "path=", "signapk_path=",
867 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700868 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700869 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
870 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800871 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700872 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700873 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700874 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800875 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700876 sys.exit(2)
877
Doug Zongkereef39442009-04-02 12:14:19 -0700878 for o, a in opts:
879 if o in ("-h", "--help"):
880 Usage(docstring)
881 sys.exit()
882 elif o in ("-v", "--verbose"):
883 OPTIONS.verbose = True
884 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700885 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700886 elif o in ("--signapk_path",):
887 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800888 elif o in ("--signapk_shared_library_path",):
889 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700890 elif o in ("--extra_signapk_args",):
891 OPTIONS.extra_signapk_args = shlex.split(a)
892 elif o in ("--java_path",):
893 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700894 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800895 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700896 elif o in ("--public_key_suffix",):
897 OPTIONS.public_key_suffix = a
898 elif o in ("--private_key_suffix",):
899 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800900 elif o in ("--boot_signer_path",):
901 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700902 elif o in ("--boot_signer_args",):
903 OPTIONS.boot_signer_args = shlex.split(a)
904 elif o in ("--verity_signer_path",):
905 OPTIONS.verity_signer_path = a
906 elif o in ("--verity_signer_args",):
907 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700908 elif o in ("-s", "--device_specific"):
909 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800910 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800911 key, value = a.split("=", 1)
912 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700913 else:
914 if extra_option_handler is None or not extra_option_handler(o, a):
915 assert False, "unknown option \"%s\"" % (o,)
916
Doug Zongker85448772014-09-09 14:59:20 -0700917 if OPTIONS.search_path:
918 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
919 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700920
921 return args
922
923
Tao Bao4c851b12016-09-19 13:54:38 -0700924def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700925 """Make a temp file and add it to the list of things to be deleted
926 when Cleanup() is called. Return the filename."""
927 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
928 os.close(fd)
929 OPTIONS.tempfiles.append(fn)
930 return fn
931
932
Doug Zongkereef39442009-04-02 12:14:19 -0700933def Cleanup():
934 for i in OPTIONS.tempfiles:
935 if os.path.isdir(i):
936 shutil.rmtree(i)
937 else:
938 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700939
940
941class PasswordManager(object):
942 def __init__(self):
943 self.editor = os.getenv("EDITOR", None)
944 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
945
946 def GetPasswords(self, items):
947 """Get passwords corresponding to each string in 'items',
948 returning a dict. (The dict may have keys in addition to the
949 values in 'items'.)
950
951 Uses the passwords in $ANDROID_PW_FILE if available, letting the
952 user edit that file to add more needed passwords. If no editor is
953 available, or $ANDROID_PW_FILE isn't define, prompts the user
954 interactively in the ordinary way.
955 """
956
957 current = self.ReadFile()
958
959 first = True
960 while True:
961 missing = []
962 for i in items:
963 if i not in current or not current[i]:
964 missing.append(i)
965 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700966 if not missing:
967 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700968
969 for i in missing:
970 current[i] = ""
971
972 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800973 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700974 answer = raw_input("try to edit again? [y]> ").strip()
975 if answer and answer[0] not in 'yY':
976 raise RuntimeError("key passwords unavailable")
977 first = False
978
979 current = self.UpdateAndReadFile(current)
980
Dan Albert8b72aef2015-03-23 19:13:21 -0700981 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700982 """Prompt the user to enter a value (password) for each key in
983 'current' whose value is fales. Returns a new dict with all the
984 values.
985 """
986 result = {}
987 for k, v in sorted(current.iteritems()):
988 if v:
989 result[k] = v
990 else:
991 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700992 result[k] = getpass.getpass(
993 "Enter password for %s key> " % k).strip()
994 if result[k]:
995 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700996 return result
997
998 def UpdateAndReadFile(self, current):
999 if not self.editor or not self.pwfile:
1000 return self.PromptResult(current)
1001
1002 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001003 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001004 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1005 f.write("# (Additional spaces are harmless.)\n\n")
1006
1007 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001008 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1009 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001010 f.write("[[[ %s ]]] %s\n" % (v, k))
1011 if not v and first_line is None:
1012 # position cursor on first line with no password.
1013 first_line = i + 4
1014 f.close()
1015
1016 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1017 _, _ = p.communicate()
1018
1019 return self.ReadFile()
1020
1021 def ReadFile(self):
1022 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001023 if self.pwfile is None:
1024 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001025 try:
1026 f = open(self.pwfile, "r")
1027 for line in f:
1028 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001029 if not line or line[0] == '#':
1030 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001031 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1032 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001033 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001034 else:
1035 result[m.group(2)] = m.group(1)
1036 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001037 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001038 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001039 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001040 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001041
1042
Dan Albert8e0178d2015-01-27 15:53:15 -08001043def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1044 compress_type=None):
1045 import datetime
1046
1047 # http://b/18015246
1048 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1049 # for files larger than 2GiB. We can work around this by adjusting their
1050 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1051 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1052 # it isn't clear to me exactly what circumstances cause this).
1053 # `zipfile.write()` must be used directly to work around this.
1054 #
1055 # This mess can be avoided if we port to python3.
1056 saved_zip64_limit = zipfile.ZIP64_LIMIT
1057 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1058
1059 if compress_type is None:
1060 compress_type = zip_file.compression
1061 if arcname is None:
1062 arcname = filename
1063
1064 saved_stat = os.stat(filename)
1065
1066 try:
1067 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1068 # file to be zipped and reset it when we're done.
1069 os.chmod(filename, perms)
1070
1071 # Use a fixed timestamp so the output is repeatable.
1072 epoch = datetime.datetime.fromtimestamp(0)
1073 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1074 os.utime(filename, (timestamp, timestamp))
1075
1076 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1077 finally:
1078 os.chmod(filename, saved_stat.st_mode)
1079 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1080 zipfile.ZIP64_LIMIT = saved_zip64_limit
1081
1082
Tao Bao58c1b962015-05-20 09:32:18 -07001083def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001084 compress_type=None):
1085 """Wrap zipfile.writestr() function to work around the zip64 limit.
1086
1087 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1088 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1089 when calling crc32(bytes).
1090
1091 But it still works fine to write a shorter string into a large zip file.
1092 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1093 when we know the string won't be too long.
1094 """
1095
1096 saved_zip64_limit = zipfile.ZIP64_LIMIT
1097 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1098
1099 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1100 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001101 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001102 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001103 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001104 else:
Tao Baof3282b42015-04-01 11:21:55 -07001105 zinfo = zinfo_or_arcname
1106
1107 # If compress_type is given, it overrides the value in zinfo.
1108 if compress_type is not None:
1109 zinfo.compress_type = compress_type
1110
Tao Bao58c1b962015-05-20 09:32:18 -07001111 # If perms is given, it has a priority.
1112 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001113 # If perms doesn't set the file type, mark it as a regular file.
1114 if perms & 0o770000 == 0:
1115 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001116 zinfo.external_attr = perms << 16
1117
Tao Baof3282b42015-04-01 11:21:55 -07001118 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001119 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1120
Dan Albert8b72aef2015-03-23 19:13:21 -07001121 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001122 zipfile.ZIP64_LIMIT = saved_zip64_limit
1123
1124
1125def ZipClose(zip_file):
1126 # http://b/18015246
1127 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1128 # central directory.
1129 saved_zip64_limit = zipfile.ZIP64_LIMIT
1130 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1131
1132 zip_file.close()
1133
1134 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001135
1136
1137class DeviceSpecificParams(object):
1138 module = None
1139 def __init__(self, **kwargs):
1140 """Keyword arguments to the constructor become attributes of this
1141 object, which is passed to all functions in the device-specific
1142 module."""
1143 for k, v in kwargs.iteritems():
1144 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001145 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001146
1147 if self.module is None:
1148 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001149 if not path:
1150 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001151 try:
1152 if os.path.isdir(path):
1153 info = imp.find_module("releasetools", [path])
1154 else:
1155 d, f = os.path.split(path)
1156 b, x = os.path.splitext(f)
1157 if x == ".py":
1158 f = b
1159 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001160 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001161 self.module = imp.load_module("device_specific", *info)
1162 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001163 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001164
1165 def _DoCall(self, function_name, *args, **kwargs):
1166 """Call the named function in the device-specific module, passing
1167 the given args and kwargs. The first argument to the call will be
1168 the DeviceSpecific object itself. If there is no module, or the
1169 module does not define the function, return the value of the
1170 'default' kwarg (which itself defaults to None)."""
1171 if self.module is None or not hasattr(self.module, function_name):
1172 return kwargs.get("default", None)
1173 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1174
1175 def FullOTA_Assertions(self):
1176 """Called after emitting the block of assertions at the top of a
1177 full OTA package. Implementations can add whatever additional
1178 assertions they like."""
1179 return self._DoCall("FullOTA_Assertions")
1180
Doug Zongkere5ff5902012-01-17 10:55:37 -08001181 def FullOTA_InstallBegin(self):
1182 """Called at the start of full OTA installation."""
1183 return self._DoCall("FullOTA_InstallBegin")
1184
Doug Zongker05d3dea2009-06-22 11:32:31 -07001185 def FullOTA_InstallEnd(self):
1186 """Called at the end of full OTA installation; typically this is
1187 used to install the image for the device's baseband processor."""
1188 return self._DoCall("FullOTA_InstallEnd")
1189
1190 def IncrementalOTA_Assertions(self):
1191 """Called after emitting the block of assertions at the top of an
1192 incremental OTA package. Implementations can add whatever
1193 additional assertions they like."""
1194 return self._DoCall("IncrementalOTA_Assertions")
1195
Doug Zongkere5ff5902012-01-17 10:55:37 -08001196 def IncrementalOTA_VerifyBegin(self):
1197 """Called at the start of the verification phase of incremental
1198 OTA installation; additional checks can be placed here to abort
1199 the script before any changes are made."""
1200 return self._DoCall("IncrementalOTA_VerifyBegin")
1201
Doug Zongker05d3dea2009-06-22 11:32:31 -07001202 def IncrementalOTA_VerifyEnd(self):
1203 """Called at the end of the verification phase of incremental OTA
1204 installation; additional checks can be placed here to abort the
1205 script before any changes are made."""
1206 return self._DoCall("IncrementalOTA_VerifyEnd")
1207
Doug Zongkere5ff5902012-01-17 10:55:37 -08001208 def IncrementalOTA_InstallBegin(self):
1209 """Called at the start of incremental OTA installation (after
1210 verification is complete)."""
1211 return self._DoCall("IncrementalOTA_InstallBegin")
1212
Doug Zongker05d3dea2009-06-22 11:32:31 -07001213 def IncrementalOTA_InstallEnd(self):
1214 """Called at the end of incremental OTA installation; typically
1215 this is used to install the image for the device's baseband
1216 processor."""
1217 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001218
Tao Bao9bc6bb22015-11-09 16:58:28 -08001219 def VerifyOTA_Assertions(self):
1220 return self._DoCall("VerifyOTA_Assertions")
1221
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001222class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001223 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001224 self.name = name
1225 self.data = data
1226 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001227 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001228 self.sha1 = sha1(data).hexdigest()
1229
1230 @classmethod
1231 def FromLocalFile(cls, name, diskname):
1232 f = open(diskname, "rb")
1233 data = f.read()
1234 f.close()
1235 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001236
1237 def WriteToTemp(self):
1238 t = tempfile.NamedTemporaryFile()
1239 t.write(self.data)
1240 t.flush()
1241 return t
1242
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001243 def WriteToDir(self, d):
1244 with open(os.path.join(d, self.name), "wb") as fp:
1245 fp.write(self.data)
1246
Geremy Condra36bd3652014-02-06 19:45:10 -08001247 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001248 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001249
1250DIFF_PROGRAM_BY_EXT = {
1251 ".gz" : "imgdiff",
1252 ".zip" : ["imgdiff", "-z"],
1253 ".jar" : ["imgdiff", "-z"],
1254 ".apk" : ["imgdiff", "-z"],
1255 ".img" : "imgdiff",
1256 }
1257
1258class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001259 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001260 self.tf = tf
1261 self.sf = sf
1262 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001263 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001264
1265 def ComputePatch(self):
1266 """Compute the patch (as a string of data) needed to turn sf into
1267 tf. Returns the same tuple as GetPatch()."""
1268
1269 tf = self.tf
1270 sf = self.sf
1271
Doug Zongker24cd2802012-08-14 16:36:15 -07001272 if self.diff_program:
1273 diff_program = self.diff_program
1274 else:
1275 ext = os.path.splitext(tf.name)[1]
1276 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001277
1278 ttemp = tf.WriteToTemp()
1279 stemp = sf.WriteToTemp()
1280
1281 ext = os.path.splitext(tf.name)[1]
1282
1283 try:
1284 ptemp = tempfile.NamedTemporaryFile()
1285 if isinstance(diff_program, list):
1286 cmd = copy.copy(diff_program)
1287 else:
1288 cmd = [diff_program]
1289 cmd.append(stemp.name)
1290 cmd.append(ttemp.name)
1291 cmd.append(ptemp.name)
1292 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001293 err = []
1294 def run():
1295 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001296 if e:
1297 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001298 th = threading.Thread(target=run)
1299 th.start()
1300 th.join(timeout=300) # 5 mins
1301 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001302 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001303 p.terminate()
1304 th.join(5)
1305 if th.is_alive():
1306 p.kill()
1307 th.join()
1308
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001309 if err or p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001310 print("WARNING: failure running %s:\n%s\n" % (
1311 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001312 self.patch = None
1313 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001314 diff = ptemp.read()
1315 finally:
1316 ptemp.close()
1317 stemp.close()
1318 ttemp.close()
1319
1320 self.patch = diff
1321 return self.tf, self.sf, self.patch
1322
1323
1324 def GetPatch(self):
1325 """Return a tuple (target_file, source_file, patch_data).
1326 patch_data may be None if ComputePatch hasn't been called, or if
1327 computing the patch failed."""
1328 return self.tf, self.sf, self.patch
1329
1330
1331def ComputeDifferences(diffs):
1332 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001333 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001334
1335 # Do the largest files first, to try and reduce the long-pole effect.
1336 by_size = [(i.tf.size, i) for i in diffs]
1337 by_size.sort(reverse=True)
1338 by_size = [i[1] for i in by_size]
1339
1340 lock = threading.Lock()
1341 diff_iter = iter(by_size) # accessed under lock
1342
1343 def worker():
1344 try:
1345 lock.acquire()
1346 for d in diff_iter:
1347 lock.release()
1348 start = time.time()
1349 d.ComputePatch()
1350 dur = time.time() - start
1351 lock.acquire()
1352
1353 tf, sf, patch = d.GetPatch()
1354 if sf.name == tf.name:
1355 name = tf.name
1356 else:
1357 name = "%s (%s)" % (tf.name, sf.name)
1358 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001359 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001360 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001361 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1362 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001363 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001364 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001365 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001366 raise
1367
1368 # start worker threads; wait for them all to finish.
1369 threads = [threading.Thread(target=worker)
1370 for i in range(OPTIONS.worker_threads)]
1371 for th in threads:
1372 th.start()
1373 while threads:
1374 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001375
1376
Dan Albert8b72aef2015-03-23 19:13:21 -07001377class BlockDifference(object):
1378 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001379 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001380 self.tgt = tgt
1381 self.src = src
1382 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001383 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001384 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001385
Tao Baodd2a5892015-03-12 12:32:37 -07001386 if version is None:
1387 version = 1
1388 if OPTIONS.info_dict:
1389 version = max(
1390 int(i) for i in
1391 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001392 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001393 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001394
1395 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001396 version=self.version,
1397 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001398 tmpdir = tempfile.mkdtemp()
1399 OPTIONS.tempfiles.append(tmpdir)
1400 self.path = os.path.join(tmpdir, partition)
1401 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001402 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001403 self.touched_src_ranges = b.touched_src_ranges
1404 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001405
Tao Baoaac4ad52015-10-16 15:26:34 -07001406 if src is None:
1407 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1408 else:
1409 _, self.device = GetTypeAndDevice("/" + partition,
1410 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001411
Tao Baod8d14be2016-02-04 14:26:02 -08001412 @property
1413 def required_cache(self):
1414 return self._required_cache
1415
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001416 def WriteScript(self, script, output_zip, progress=None):
1417 if not self.src:
1418 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001419 script.Print("Patching %s image unconditionally..." % (self.partition,))
1420 else:
1421 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001422
Dan Albert8b72aef2015-03-23 19:13:21 -07001423 if progress:
1424 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001425 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001426 if OPTIONS.verify:
1427 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001428
Tao Bao9bc6bb22015-11-09 16:58:28 -08001429 def WriteStrictVerifyScript(self, script):
1430 """Verify all the blocks in the care_map, including clobbered blocks.
1431
1432 This differs from the WriteVerifyScript() function: a) it prints different
1433 error messages; b) it doesn't allow half-way updated images to pass the
1434 verification."""
1435
1436 partition = self.partition
1437 script.Print("Verifying %s..." % (partition,))
1438 ranges = self.tgt.care_map
1439 ranges_str = ranges.to_string_raw()
1440 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1441 'ui_print(" Verified.") || '
1442 'ui_print("\\"%s\\" has unexpected contents.");' % (
1443 self.device, ranges_str,
1444 self.tgt.TotalSha1(include_clobbered_blocks=True),
1445 self.device))
1446 script.AppendExtra("")
1447
Tao Baod522bdc2016-04-12 15:53:16 -07001448 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001449 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001450
1451 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001452 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001453 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001454
1455 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001456 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001457 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001458 ranges = self.touched_src_ranges
1459 expected_sha1 = self.touched_src_sha1
1460 else:
1461 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1462 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001463
1464 # No blocks to be checked, skipping.
1465 if not ranges:
1466 return
1467
Tao Bao5ece99d2015-05-12 11:42:31 -07001468 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001469 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1470 'block_image_verify("%s", '
1471 'package_extract_file("%s.transfer.list"), '
1472 '"%s.new.dat", "%s.patch.dat")) then') % (
1473 self.device, ranges_str, expected_sha1,
1474 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001475 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001476 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001477
Tianjie Xufc3422a2015-12-15 11:53:59 -08001478 if self.version >= 4:
1479
1480 # Bug: 21124327
1481 # When generating incrementals for the system and vendor partitions in
1482 # version 4 or newer, explicitly check the first block (which contains
1483 # the superblock) of the partition to see if it's what we expect. If
1484 # this check fails, give an explicit log message about the partition
1485 # having been remounted R/W (the most likely explanation).
1486 if self.check_first_block:
1487 script.AppendExtra('check_first_block("%s");' % (self.device,))
1488
1489 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001490 if partition == "system":
1491 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1492 else:
1493 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001494 script.AppendExtra((
1495 'ifelse (block_image_recover("{device}", "{ranges}") && '
1496 'block_image_verify("{device}", '
1497 'package_extract_file("{partition}.transfer.list"), '
1498 '"{partition}.new.dat", "{partition}.patch.dat"), '
1499 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001500 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001501 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001502 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001503
Tao Baodd2a5892015-03-12 12:32:37 -07001504 # Abort the OTA update. Note that the incremental OTA cannot be applied
1505 # even if it may match the checksum of the target partition.
1506 # a) If version < 3, operations like move and erase will make changes
1507 # unconditionally and damage the partition.
1508 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001509 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001510 if partition == "system":
1511 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1512 else:
1513 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1514 script.AppendExtra((
1515 'abort("E%d: %s partition has unexpected contents");\n'
1516 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001517
Tao Bao5fcaaef2015-06-01 13:40:49 -07001518 def _WritePostInstallVerifyScript(self, script):
1519 partition = self.partition
1520 script.Print('Verifying the updated %s image...' % (partition,))
1521 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1522 ranges = self.tgt.care_map
1523 ranges_str = ranges.to_string_raw()
1524 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1525 self.device, ranges_str,
1526 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001527
1528 # Bug: 20881595
1529 # Verify that extended blocks are really zeroed out.
1530 if self.tgt.extended:
1531 ranges_str = self.tgt.extended.to_string_raw()
1532 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1533 self.device, ranges_str,
1534 self._HashZeroBlocks(self.tgt.extended.size())))
1535 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001536 if partition == "system":
1537 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1538 else:
1539 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001540 script.AppendExtra(
1541 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001542 ' abort("E%d: %s partition has unexpected non-zero contents after '
1543 'OTA update");\n'
1544 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001545 else:
1546 script.Print('Verified the updated %s image.' % (partition,))
1547
Tianjie Xu209db462016-05-24 17:34:52 -07001548 if partition == "system":
1549 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1550 else:
1551 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1552
Tao Bao5fcaaef2015-06-01 13:40:49 -07001553 script.AppendExtra(
1554 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001555 ' abort("E%d: %s partition has unexpected contents after OTA '
1556 'update");\n'
1557 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001558
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001559 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001560 ZipWrite(output_zip,
1561 '{}.transfer.list'.format(self.path),
1562 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001563
1564 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1565 # almost triples the compression time but doesn't further reduce the size too much.
1566 # For a typical 1.8G system.new.dat
1567 # zip | brotli(quality 6) | brotli(quality 9)
1568 # compressed_size: 942M | 869M (~8% reduced) | 854M
1569 # compression_time: 75s | 265s | 719s
1570 # decompression_time: 15s | 25s | 25s
1571
1572 if not self.src:
1573 bro_cmd = ['bro', '--quality', '6',
1574 '--input', '{}.new.dat'.format(self.path),
1575 '--output', '{}.new.dat.br'.format(self.path)]
1576 print("Compressing {}.new.dat with brotli".format(self.partition))
1577 p = Run(bro_cmd, stdout=subprocess.PIPE)
1578 p.communicate()
1579 assert p.returncode == 0,\
1580 'compression of {}.new.dat failed'.format(self.partition)
1581
1582 new_data_name = '{}.new.dat.br'.format(self.partition)
1583 ZipWrite(output_zip,
1584 '{}.new.dat.br'.format(self.path),
1585 new_data_name,
1586 compress_type=zipfile.ZIP_STORED)
1587 else:
1588 new_data_name = '{}.new.dat'.format(self.partition)
1589 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1590
Dan Albert8e0178d2015-01-27 15:53:15 -08001591 ZipWrite(output_zip,
1592 '{}.patch.dat'.format(self.path),
1593 '{}.patch.dat'.format(self.partition),
1594 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001595
Tianjie Xu209db462016-05-24 17:34:52 -07001596 if self.partition == "system":
1597 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1598 else:
1599 code = ErrorCode.VENDOR_UPDATE_FAILURE
1600
Dan Albert8e0178d2015-01-27 15:53:15 -08001601 call = ('block_image_update("{device}", '
1602 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001603 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001604 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001605 device=self.device, partition=self.partition,
1606 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001607 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001608
Dan Albert8b72aef2015-03-23 19:13:21 -07001609 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001610 data = source.ReadRangeSet(ranges)
1611 ctx = sha1()
1612
1613 for p in data:
1614 ctx.update(p)
1615
1616 return ctx.hexdigest()
1617
Tao Baoe9b61912015-07-09 17:37:49 -07001618 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1619 """Return the hash value for all zero blocks."""
1620 zero_block = '\x00' * 4096
1621 ctx = sha1()
1622 for _ in range(num_blocks):
1623 ctx.update(zero_block)
1624
1625 return ctx.hexdigest()
1626
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001627
1628DataImage = blockimgdiff.DataImage
1629
Doug Zongker96a57e72010-09-26 14:57:41 -07001630# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001631PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001632 "ext4": "EMMC",
1633 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001634 "f2fs": "EMMC",
1635 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001636}
Doug Zongker96a57e72010-09-26 14:57:41 -07001637
1638def GetTypeAndDevice(mount_point, info):
1639 fstab = info["fstab"]
1640 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001641 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1642 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001643 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001644 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001645
1646
1647def ParseCertificate(data):
1648 """Parse a PEM-format certificate."""
1649 cert = []
1650 save = False
1651 for line in data.split("\n"):
1652 if "--END CERTIFICATE--" in line:
1653 break
1654 if save:
1655 cert.append(line)
1656 if "--BEGIN CERTIFICATE--" in line:
1657 save = True
1658 cert = "".join(cert).decode('base64')
1659 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001660
Doug Zongker412c02f2014-02-13 10:58:24 -08001661def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1662 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001663 """Generate a binary patch that creates the recovery image starting
1664 with the boot image. (Most of the space in these images is just the
1665 kernel, which is identical for the two, so the resulting patch
1666 should be efficient.) Add it to the output zip, along with a shell
1667 script that is run from init.rc on first boot to actually do the
1668 patching and install the new recovery image.
1669
1670 recovery_img and boot_img should be File objects for the
1671 corresponding images. info should be the dictionary returned by
1672 common.LoadInfoDict() on the input target_files.
1673 """
1674
Doug Zongker412c02f2014-02-13 10:58:24 -08001675 if info_dict is None:
1676 info_dict = OPTIONS.info_dict
1677
Tao Baof2cffbd2015-07-22 12:33:18 -07001678 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001679
Tao Baof2cffbd2015-07-22 12:33:18 -07001680 if full_recovery_image:
1681 output_sink("etc/recovery.img", recovery_img.data)
1682
1683 else:
1684 diff_program = ["imgdiff"]
1685 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1686 if os.path.exists(path):
1687 diff_program.append("-b")
1688 diff_program.append(path)
1689 bonus_args = "-b /system/etc/recovery-resource.dat"
1690 else:
1691 bonus_args = ""
1692
1693 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1694 _, _, patch = d.ComputePatch()
1695 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001696
Dan Albertebb19aa2015-03-27 19:11:53 -07001697 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001698 # The following GetTypeAndDevice()s need to use the path in the target
1699 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001700 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1701 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1702 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001703 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001704
Tao Baof2cffbd2015-07-22 12:33:18 -07001705 if full_recovery_image:
1706 sh = """#!/system/bin/sh
1707if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1708 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"
1709else
1710 log -t recovery "Recovery image already installed"
1711fi
1712""" % {'type': recovery_type,
1713 'device': recovery_device,
1714 'sha1': recovery_img.sha1,
1715 'size': recovery_img.size}
1716 else:
1717 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001718if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1719 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"
1720else
1721 log -t recovery "Recovery image already installed"
1722fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001723""" % {'boot_size': boot_img.size,
1724 'boot_sha1': boot_img.sha1,
1725 'recovery_size': recovery_img.size,
1726 'recovery_sha1': recovery_img.sha1,
1727 'boot_type': boot_type,
1728 'boot_device': boot_device,
1729 'recovery_type': recovery_type,
1730 'recovery_device': recovery_device,
1731 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001732
1733 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001734 # in the L release.
1735 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001736
Tao Bao89fbb0f2017-01-10 10:47:58 -08001737 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001738
1739 output_sink(sh_location, sh)