blob: 829b8db9a5e9d0f5659fcb29fac67a384e2afb5f [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
Tao Bao9dd909e2017-11-14 11:27:32 -080079
80# The partitions allowed to be signed by AVB (Android verified boot 2.0).
81AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'dtbo')
82
83
Tianjie Xu209db462016-05-24 17:34:52 -070084class ErrorCode(object):
85 """Define error_codes for failures that happen during the actual
86 update package installation.
87
88 Error codes 0-999 are reserved for failures before the package
89 installation (i.e. low battery, package verification failure).
90 Detailed code in 'bootable/recovery/error_code.h' """
91
92 SYSTEM_VERIFICATION_FAILURE = 1000
93 SYSTEM_UPDATE_FAILURE = 1001
94 SYSTEM_UNEXPECTED_CONTENTS = 1002
95 SYSTEM_NONZERO_CONTENTS = 1003
96 SYSTEM_RECOVER_FAILURE = 1004
97 VENDOR_VERIFICATION_FAILURE = 2000
98 VENDOR_UPDATE_FAILURE = 2001
99 VENDOR_UNEXPECTED_CONTENTS = 2002
100 VENDOR_NONZERO_CONTENTS = 2003
101 VENDOR_RECOVER_FAILURE = 2004
102 OEM_PROP_MISMATCH = 3000
103 FINGERPRINT_MISMATCH = 3001
104 THUMBPRINT_MISMATCH = 3002
105 OLDER_BUILD = 3003
106 DEVICE_MISMATCH = 3004
107 BAD_PATCH_FILE = 3005
108 INSUFFICIENT_CACHE_SPACE = 3006
109 TUNE_PARTITION_FAILURE = 3007
110 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800111
Dan Albert8b72aef2015-03-23 19:13:21 -0700112class ExternalError(RuntimeError):
113 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700114
115
Tao Bao39451582017-05-04 11:10:47 -0700116def Run(args, verbose=None, **kwargs):
117 """Create and return a subprocess.Popen object.
118
119 Caller can specify if the command line should be printed. The global
120 OPTIONS.verbose will be used if not specified.
121 """
122 if verbose is None:
123 verbose = OPTIONS.verbose
124 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800125 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700126 return subprocess.Popen(args, **kwargs)
127
128
Ying Wang7e6d4e42010-12-13 16:25:36 -0800129def CloseInheritedPipes():
130 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
131 before doing other work."""
132 if platform.system() != "Darwin":
133 return
134 for d in range(3, 1025):
135 try:
136 stat = os.fstat(d)
137 if stat is not None:
138 pipebit = stat[0] & 0x1000
139 if pipebit != 0:
140 os.close(d)
141 except OSError:
142 pass
143
144
Tao Bao2c15d9e2015-07-09 11:51:16 -0700145def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700146 """Read and parse the META/misc_info.txt key/value pairs from the
147 input target files and return a dict."""
148
Doug Zongkerc9253822014-02-04 12:17:58 -0800149 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700150 if isinstance(input_file, zipfile.ZipFile):
151 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800152 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700153 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800154 try:
155 with open(path) as f:
156 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700157 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800158 if e.errno == errno.ENOENT:
159 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800160
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700161 try:
Michael Runge6e836112014-04-15 17:40:21 -0700162 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700163 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800164 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700165
Tao Bao6cd54732017-02-27 15:12:05 -0800166 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800167 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800168
Tao Bao84e75682015-07-19 02:38:53 -0700169 # A few properties are stored as links to the files in the out/ directory.
170 # It works fine with the build system. However, they are no longer available
171 # when (re)generating from target_files zip. If input_dir is not None, we
172 # are doing repacking. Redirect those properties to the actual files in the
173 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700174 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400175 # We carry a copy of file_contexts.bin under META/. If not available,
176 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700177 # to build images than the one running on device, such as when enabling
178 # system_root_image. In that case, we must have the one for image
179 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700180 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
181 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700182 if d.get("system_root_image") == "true":
183 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700184 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700185 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700186 if not os.path.exists(fc_config):
187 fc_config = None
188
189 if fc_config:
190 d["selinux_fc"] = fc_config
191
Tao Bao84e75682015-07-19 02:38:53 -0700192 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
193 if d.get("system_root_image") == "true":
194 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
195 d["ramdisk_fs_config"] = os.path.join(
196 input_dir, "META", "root_filesystem_config.txt")
197
Tao Baof54216f2016-03-29 15:12:37 -0700198 # Redirect {system,vendor}_base_fs_file.
199 if "system_base_fs_file" in d:
200 basename = os.path.basename(d["system_base_fs_file"])
201 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700202 if os.path.exists(system_base_fs_file):
203 d["system_base_fs_file"] = system_base_fs_file
204 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800205 print("Warning: failed to find system base fs file: %s" % (
206 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700207 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700208
209 if "vendor_base_fs_file" in d:
210 basename = os.path.basename(d["vendor_base_fs_file"])
211 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700212 if os.path.exists(vendor_base_fs_file):
213 d["vendor_base_fs_file"] = vendor_base_fs_file
214 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800215 print("Warning: failed to find vendor base fs file: %s" % (
216 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700217 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700218
Doug Zongker37974732010-09-16 17:44:38 -0700219 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800220 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700221 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700222 if not line:
223 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700224 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700225 if not value:
226 continue
Doug Zongker37974732010-09-16 17:44:38 -0700227 if name == "blocksize":
228 d[name] = value
229 else:
230 d[name + "_size"] = value
231 except KeyError:
232 pass
233
234 def makeint(key):
235 if key in d:
236 d[key] = int(d[key], 0)
237
238 makeint("recovery_api_version")
239 makeint("blocksize")
240 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700241 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700242 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700243 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700244 makeint("recovery_size")
245 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800246 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700247
Tianjie Xucfa86222016-03-07 16:31:19 -0800248 system_root_image = d.get("system_root_image", None) == "true"
249 if d.get("no_recovery", None) != "true":
250 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800251 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800252 recovery_fstab_path, system_root_image)
253 elif d.get("recovery_as_boot", None) == "true":
254 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
255 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
256 recovery_fstab_path, system_root_image)
257 else:
258 d["fstab"] = None
259
Tao Baobcd1d162017-08-26 13:10:26 -0700260 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
261 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700262 return d
263
Tao Baod1de6f32017-03-01 16:38:48 -0800264
Tao Baobcd1d162017-08-26 13:10:26 -0700265def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700266 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700267 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700268 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700269 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700270 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700271 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700272
Tao Baod1de6f32017-03-01 16:38:48 -0800273
Michael Runge6e836112014-04-15 17:40:21 -0700274def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700275 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700276 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700277 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700278 if not line or line.startswith("#"):
279 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700280 if "=" in line:
281 name, value = line.split("=", 1)
282 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700283 return d
284
Tao Baod1de6f32017-03-01 16:38:48 -0800285
Tianjie Xucfa86222016-03-07 16:31:19 -0800286def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
287 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700288 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800289 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700290 self.mount_point = mount_point
291 self.fs_type = fs_type
292 self.device = device
293 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700294 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700295
296 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800297 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700298 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800299 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700300 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700301
Tao Baod1de6f32017-03-01 16:38:48 -0800302 assert fstab_version == 2
303
304 d = {}
305 for line in data.split("\n"):
306 line = line.strip()
307 if not line or line.startswith("#"):
308 continue
309
310 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
311 pieces = line.split()
312 if len(pieces) != 5:
313 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
314
315 # Ignore entries that are managed by vold.
316 options = pieces[4]
317 if "voldmanaged=" in options:
318 continue
319
320 # It's a good line, parse it.
321 length = 0
322 options = options.split(",")
323 for i in options:
324 if i.startswith("length="):
325 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800326 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800327 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700328 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800329
Tao Baod1de6f32017-03-01 16:38:48 -0800330 mount_flags = pieces[3]
331 # Honor the SELinux context if present.
332 context = None
333 for i in mount_flags.split(","):
334 if i.startswith("context="):
335 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800336
Tao Baod1de6f32017-03-01 16:38:48 -0800337 mount_point = pieces[1]
338 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
339 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800340
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700341 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700342 # system. Other areas assume system is always at "/system" so point /system
343 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700344 if system_root_image:
345 assert not d.has_key("/system") and d.has_key("/")
346 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700347 return d
348
349
Doug Zongker37974732010-09-16 17:44:38 -0700350def DumpInfoDict(d):
351 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800352 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700353
Dan Albert8b72aef2015-03-23 19:13:21 -0700354
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800355def AppendAVBSigningArgs(cmd, partition):
356 """Append signing arguments for avbtool."""
357 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
358 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
359 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
360 if key_path and algorithm:
361 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700362 avb_salt = OPTIONS.info_dict.get("avb_salt")
363 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
364 if avb_salt and partition != "vbmeta":
365 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800366
367
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700368def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800369 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700370 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700371
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700372 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800373 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
374 we are building a two-step special image (i.e. building a recovery image to
375 be loaded into /boot in two-step OTAs).
376
377 Return the image data, or None if sourcedir does not appear to contains files
378 for building the requested image.
379 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700380
381 def make_ramdisk():
382 ramdisk_img = tempfile.NamedTemporaryFile()
383
384 if os.access(fs_config_file, os.F_OK):
385 cmd = ["mkbootfs", "-f", fs_config_file,
386 os.path.join(sourcedir, "RAMDISK")]
387 else:
388 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
389 p1 = Run(cmd, stdout=subprocess.PIPE)
390 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
391
392 p2.wait()
393 p1.wait()
394 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
395 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
396
397 return ramdisk_img
398
399 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
400 return None
401
402 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700403 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700404
Doug Zongkerd5131602012-08-02 14:46:42 -0700405 if info_dict is None:
406 info_dict = OPTIONS.info_dict
407
Doug Zongkereef39442009-04-02 12:14:19 -0700408 img = tempfile.NamedTemporaryFile()
409
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700410 if has_ramdisk:
411 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700412
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800413 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
414 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
415
416 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700417
Benoit Fradina45a8682014-07-14 21:00:43 +0200418 fn = os.path.join(sourcedir, "second")
419 if os.access(fn, os.F_OK):
420 cmd.append("--second")
421 cmd.append(fn)
422
Doug Zongker171f1cd2009-06-15 22:36:37 -0700423 fn = os.path.join(sourcedir, "cmdline")
424 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700425 cmd.append("--cmdline")
426 cmd.append(open(fn).read().rstrip("\n"))
427
428 fn = os.path.join(sourcedir, "base")
429 if os.access(fn, os.F_OK):
430 cmd.append("--base")
431 cmd.append(open(fn).read().rstrip("\n"))
432
Ying Wang4de6b5b2010-08-25 14:29:34 -0700433 fn = os.path.join(sourcedir, "pagesize")
434 if os.access(fn, os.F_OK):
435 cmd.append("--pagesize")
436 cmd.append(open(fn).read().rstrip("\n"))
437
Doug Zongkerd5131602012-08-02 14:46:42 -0700438 args = info_dict.get("mkbootimg_args", None)
439 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700440 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700441
Sami Tolvanen3303d902016-03-15 16:49:30 +0000442 args = info_dict.get("mkbootimg_version_args", None)
443 if args and args.strip():
444 cmd.extend(shlex.split(args))
445
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700446 if has_ramdisk:
447 cmd.extend(["--ramdisk", ramdisk_img.name])
448
Tao Baod95e9fd2015-03-29 23:07:41 -0700449 img_unsigned = None
450 if info_dict.get("vboot", None):
451 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700452 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700453 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700454 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700455
Tao Baobf70c312017-07-11 17:27:55 -0700456 # "boot" or "recovery", without extension.
457 partition_name = os.path.basename(sourcedir).lower()
458
Doug Zongker38a649f2009-06-17 09:07:09 -0700459 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700460 p.communicate()
Tao Baobf70c312017-07-11 17:27:55 -0700461 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700462
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100463 if (info_dict.get("boot_signer", None) == "true" and
464 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800465 # Hard-code the path as "/boot" for two-step special recovery image (which
466 # will be loaded into /boot during the two-step OTA).
467 if two_step_image:
468 path = "/boot"
469 else:
Tao Baobf70c312017-07-11 17:27:55 -0700470 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700471 cmd = [OPTIONS.boot_signer_path]
472 cmd.extend(OPTIONS.boot_signer_args)
473 cmd.extend([path, img.name,
474 info_dict["verity_key"] + ".pk8",
475 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700476 p = Run(cmd, stdout=subprocess.PIPE)
477 p.communicate()
478 assert p.returncode == 0, "boot_signer of %s image failed" % path
479
Tao Baod95e9fd2015-03-29 23:07:41 -0700480 # Sign the image if vboot is non-empty.
481 elif info_dict.get("vboot", None):
Tao Baobf70c312017-07-11 17:27:55 -0700482 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700483 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800484 # We have switched from the prebuilt futility binary to using the tool
485 # (futility-host) built from the source. Override the setting in the old
486 # TF.zip.
487 futility = info_dict["futility"]
488 if futility.startswith("prebuilts/"):
489 futility = "futility-host"
490 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700491 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700492 info_dict["vboot_key"] + ".vbprivk",
493 info_dict["vboot_subkey"] + ".vbprivk",
494 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700495 img.name]
496 p = Run(cmd, stdout=subprocess.PIPE)
497 p.communicate()
498 assert p.returncode == 0, "vboot_signer of %s image failed" % path
499
Tao Baof3282b42015-04-01 11:21:55 -0700500 # Clean up the temp files.
501 img_unsigned.close()
502 img_keyblock.close()
503
David Zeuthen8fecb282017-12-01 16:24:01 -0500504 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800505 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700506 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500507 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400508 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700509 "--partition_size", str(part_size), "--partition_name",
510 partition_name]
511 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500512 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400513 if args and args.strip():
514 cmd.extend(shlex.split(args))
515 p = Run(cmd, stdout=subprocess.PIPE)
516 p.communicate()
517 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c312017-07-11 17:27:55 -0700518 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500519
520 img.seek(os.SEEK_SET, 0)
521 data = img.read()
522
523 if has_ramdisk:
524 ramdisk_img.close()
525 img.close()
526
527 return data
528
529
Doug Zongkerd5131602012-08-02 14:46:42 -0700530def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800531 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700532 """Return a File object with the desired bootable image.
533
534 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
535 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
536 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700537
Doug Zongker55d93282011-01-25 17:03:34 -0800538 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
539 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800540 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800541 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700542
543 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
544 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800545 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700546 return File.FromLocalFile(name, prebuilt_path)
547
Tao Bao89fbb0f2017-01-10 10:47:58 -0800548 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700549
550 if info_dict is None:
551 info_dict = OPTIONS.info_dict
552
553 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800554 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
555 # for recovery.
556 has_ramdisk = (info_dict.get("system_root_image") != "true" or
557 prebuilt_name != "boot.img" or
558 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700559
Doug Zongker6f1d0312014-08-22 08:07:12 -0700560 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400561 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
562 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800563 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700564 if data:
565 return File(name, data)
566 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800567
Doug Zongkereef39442009-04-02 12:14:19 -0700568
Narayan Kamatha07bf042017-08-14 14:49:21 +0100569def Gunzip(in_filename, out_filename):
570 """Gunzip the given gzip compressed file to a given output file.
571 """
572 with gzip.open(in_filename, "rb") as in_file, open(out_filename, "wb") as out_file:
573 shutil.copyfileobj(in_file, out_file)
574
575
Doug Zongker75f17362009-12-08 13:46:44 -0800576def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800577 """Unzip the given archive into a temporary directory and return the name.
578
579 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
580 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
581
582 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
583 main file), open for reading.
584 """
Doug Zongkereef39442009-04-02 12:14:19 -0700585
586 tmp = tempfile.mkdtemp(prefix="targetfiles-")
587 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800588
589 def unzip_to_dir(filename, dirname):
590 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
591 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800592 cmd.extend(pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800593 p = Run(cmd, stdout=subprocess.PIPE)
594 p.communicate()
595 if p.returncode != 0:
596 raise ExternalError("failed to unzip input target-files \"%s\"" %
597 (filename,))
598
599 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
600 if m:
601 unzip_to_dir(m.group(1), tmp)
602 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
603 filename = m.group(1)
604 else:
605 unzip_to_dir(filename, tmp)
606
607 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700608
609
610def GetKeyPasswords(keylist):
611 """Given a list of keys, prompt the user to enter passwords for
612 those which require them. Return a {key: password} dict. password
613 will be None if the key has no password."""
614
Doug Zongker8ce7c252009-05-22 13:34:54 -0700615 no_passwords = []
616 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700617 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700618 devnull = open("/dev/null", "w+b")
619 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800620 # We don't need a password for things that aren't really keys.
621 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700622 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700623 continue
624
T.R. Fullhart37e10522013-03-18 10:31:26 -0700625 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700626 "-inform", "DER", "-nocrypt"],
627 stdin=devnull.fileno(),
628 stdout=devnull.fileno(),
629 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700630 p.communicate()
631 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700632 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700633 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700634 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700635 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
636 "-inform", "DER", "-passin", "pass:"],
637 stdin=devnull.fileno(),
638 stdout=devnull.fileno(),
639 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700640 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700641 if p.returncode == 0:
642 # Encrypted key with empty string as password.
643 key_passwords[k] = ''
644 elif stderr.startswith('Error decrypting key'):
645 # Definitely encrypted key.
646 # It would have said "Error reading key" if it didn't parse correctly.
647 need_passwords.append(k)
648 else:
649 # Potentially, a type of key that openssl doesn't understand.
650 # We'll let the routines in signapk.jar handle it.
651 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700652 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700653
T.R. Fullhart37e10522013-03-18 10:31:26 -0700654 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700655 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700656 return key_passwords
657
658
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800659def GetMinSdkVersion(apk_name):
660 """Get the minSdkVersion delared in the APK. This can be both a decimal number
661 (API Level) or a codename.
662 """
663
664 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
665 output, err = p.communicate()
666 if err:
667 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
668 % (p.returncode,))
669
670 for line in output.split("\n"):
671 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
672 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
673 if m:
674 return m.group(1)
675 raise ExternalError("No minSdkVersion returned by aapt")
676
677
678def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
679 """Get the minSdkVersion declared in the APK as a number (API Level). If
680 minSdkVersion is set to a codename, it is translated to a number using the
681 provided map.
682 """
683
684 version = GetMinSdkVersion(apk_name)
685 try:
686 return int(version)
687 except ValueError:
688 # Not a decimal number. Codename?
689 if version in codename_to_api_level_map:
690 return codename_to_api_level_map[version]
691 else:
692 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
693 % (version, codename_to_api_level_map))
694
695
696def SignFile(input_name, output_name, key, password, min_api_level=None,
697 codename_to_api_level_map=dict(),
698 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700699 """Sign the input_name zip/jar/apk, producing output_name. Use the
700 given key and password (the latter may be None if the key does not
701 have a password.
702
Doug Zongker951495f2009-08-14 12:44:19 -0700703 If whole_file is true, use the "-w" option to SignApk to embed a
704 signature that covers the whole file in the archive comment of the
705 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800706
707 min_api_level is the API Level (int) of the oldest platform this file may end
708 up on. If not specified for an APK, the API Level is obtained by interpreting
709 the minSdkVersion attribute of the APK's AndroidManifest.xml.
710
711 codename_to_api_level_map is needed to translate the codename which may be
712 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700713 """
Doug Zongker951495f2009-08-14 12:44:19 -0700714
Alex Klyubin9667b182015-12-10 13:38:50 -0800715 java_library_path = os.path.join(
716 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
717
Tao Baoe95540e2016-11-08 12:08:53 -0800718 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
719 ["-Djava.library.path=" + java_library_path,
720 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
721 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700722 if whole_file:
723 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800724
725 min_sdk_version = min_api_level
726 if min_sdk_version is None:
727 if not whole_file:
728 min_sdk_version = GetMinSdkVersionInt(
729 input_name, codename_to_api_level_map)
730 if min_sdk_version is not None:
731 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
732
T.R. Fullhart37e10522013-03-18 10:31:26 -0700733 cmd.extend([key + OPTIONS.public_key_suffix,
734 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800735 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700736
737 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700738 if password is not None:
739 password += "\n"
740 p.communicate(password)
741 if p.returncode != 0:
742 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
743
Doug Zongkereef39442009-04-02 12:14:19 -0700744
Doug Zongker37974732010-09-16 17:44:38 -0700745def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800746 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700747
Tao Bao9dd909e2017-11-14 11:27:32 -0800748 For non-AVB images, raise exception if the data is too big. Print a warning
749 if the data is nearing the maximum size.
750
751 For AVB images, the actual image size should be identical to the limit.
752
753 Args:
754 data: A string that contains all the data for the partition.
755 target: The partition name. The ".img" suffix is optional.
756 info_dict: The dict to be looked up for relevant info.
757 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700758 if target.endswith(".img"):
759 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700760 mount_point = "/" + target
761
Ying Wangf8824af2014-06-03 14:07:27 -0700762 fs_type = None
763 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700764 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700765 if mount_point == "/userdata":
766 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700767 p = info_dict["fstab"][mount_point]
768 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800769 device = p.device
770 if "/" in device:
771 device = device[device.rfind("/")+1:]
772 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700773 if not fs_type or not limit:
774 return
Doug Zongkereef39442009-04-02 12:14:19 -0700775
Andrew Boie0f9aec82012-02-14 09:32:52 -0800776 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800777 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
778 # path.
779 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
780 if size != limit:
781 raise ExternalError(
782 "Mismatching image size for %s: expected %d actual %d" % (
783 target, limit, size))
784 else:
785 pct = float(size) * 100.0 / limit
786 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
787 if pct >= 99.0:
788 raise ExternalError(msg)
789 elif pct >= 95.0:
790 print("\n WARNING: %s\n" % (msg,))
791 elif OPTIONS.verbose:
792 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700793
794
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800795def ReadApkCerts(tf_zip):
796 """Given a target_files ZipFile, parse the META/apkcerts.txt file
Narayan Kamatha07bf042017-08-14 14:49:21 +0100797 and return a tuple with the following elements: (1) a dictionary that maps
798 packages to certs (based on the "certificate" and "private_key" attributes
799 in the file. (2) A string representing the extension of compressed APKs in
800 the target files (e.g ".gz" ".bro")."""
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800801 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100802 compressed_extension = None
803
Tao Bao0f990332017-09-08 19:02:54 -0700804 # META/apkcerts.txt contains the info for _all_ the packages known at build
805 # time. Filter out the ones that are not installed.
806 installed_files = set()
807 for name in tf_zip.namelist():
808 basename = os.path.basename(name)
809 if basename:
810 installed_files.add(basename)
811
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800812 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
813 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700814 if not line:
815 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100816 m = re.match(r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
817 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
818 line)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800819 if m:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100820 matches = m.groupdict()
821 cert = matches["CERT"]
822 privkey = matches["PRIVKEY"]
823 name = matches["NAME"]
824 this_compressed_extension = matches["COMPRESSED"]
T.R. Fullhart37e10522013-03-18 10:31:26 -0700825 public_key_suffix_len = len(OPTIONS.public_key_suffix)
826 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800827 if cert in SPECIAL_CERT_STRINGS and not privkey:
828 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700829 elif (cert.endswith(OPTIONS.public_key_suffix) and
830 privkey.endswith(OPTIONS.private_key_suffix) and
831 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
832 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800833 else:
834 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
Narayan Kamatha07bf042017-08-14 14:49:21 +0100835 if this_compressed_extension:
Tao Bao0f990332017-09-08 19:02:54 -0700836 # Only count the installed files.
837 filename = name + '.' + this_compressed_extension
838 if filename not in installed_files:
839 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100840 # Make sure that all the values in the compression map have the same
841 # extension. We don't support multiple compression methods in the same
842 # system image.
843 if compressed_extension:
844 if this_compressed_extension != compressed_extension:
845 raise ValueError("multiple compressed extensions : %s vs %s",
846 (compressed_extension, this_compressed_extension))
847 else:
848 compressed_extension = this_compressed_extension
849
850 return (certmap, ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800851
852
Doug Zongkereef39442009-04-02 12:14:19 -0700853COMMON_DOCSTRING = """
854 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700855 Prepend <dir>/bin to the list of places to search for binaries
856 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700857
Doug Zongker05d3dea2009-06-22 11:32:31 -0700858 -s (--device_specific) <file>
859 Path to the python module containing device-specific
860 releasetools code.
861
Doug Zongker8bec09e2009-11-30 15:37:14 -0800862 -x (--extra) <key=value>
863 Add a key/value pair to the 'extras' dict, which device-specific
864 extension code may look at.
865
Doug Zongkereef39442009-04-02 12:14:19 -0700866 -v (--verbose)
867 Show command lines being executed.
868
869 -h (--help)
870 Display this usage message and exit.
871"""
872
873def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800874 print(docstring.rstrip("\n"))
875 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700876
877
878def ParseOptions(argv,
879 docstring,
880 extra_opts="", extra_long_opts=(),
881 extra_option_handler=None):
882 """Parse the options in argv and return any arguments that aren't
883 flags. docstring is the calling module's docstring, to be displayed
884 for errors and -h. extra_opts and extra_long_opts are for flags
885 defined by the caller, which are processed by passing them to
886 extra_option_handler."""
887
888 try:
889 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800890 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800891 ["help", "verbose", "path=", "signapk_path=",
892 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700893 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700894 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
895 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800896 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700897 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700898 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700899 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800900 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700901 sys.exit(2)
902
Doug Zongkereef39442009-04-02 12:14:19 -0700903 for o, a in opts:
904 if o in ("-h", "--help"):
905 Usage(docstring)
906 sys.exit()
907 elif o in ("-v", "--verbose"):
908 OPTIONS.verbose = True
909 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700910 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700911 elif o in ("--signapk_path",):
912 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800913 elif o in ("--signapk_shared_library_path",):
914 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700915 elif o in ("--extra_signapk_args",):
916 OPTIONS.extra_signapk_args = shlex.split(a)
917 elif o in ("--java_path",):
918 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700919 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800920 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700921 elif o in ("--public_key_suffix",):
922 OPTIONS.public_key_suffix = a
923 elif o in ("--private_key_suffix",):
924 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800925 elif o in ("--boot_signer_path",):
926 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700927 elif o in ("--boot_signer_args",):
928 OPTIONS.boot_signer_args = shlex.split(a)
929 elif o in ("--verity_signer_path",):
930 OPTIONS.verity_signer_path = a
931 elif o in ("--verity_signer_args",):
932 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700933 elif o in ("-s", "--device_specific"):
934 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800935 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800936 key, value = a.split("=", 1)
937 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700938 else:
939 if extra_option_handler is None or not extra_option_handler(o, a):
940 assert False, "unknown option \"%s\"" % (o,)
941
Doug Zongker85448772014-09-09 14:59:20 -0700942 if OPTIONS.search_path:
943 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
944 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700945
946 return args
947
948
Tao Bao4c851b12016-09-19 13:54:38 -0700949def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700950 """Make a temp file and add it to the list of things to be deleted
951 when Cleanup() is called. Return the filename."""
952 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
953 os.close(fd)
954 OPTIONS.tempfiles.append(fn)
955 return fn
956
957
Doug Zongkereef39442009-04-02 12:14:19 -0700958def Cleanup():
959 for i in OPTIONS.tempfiles:
960 if os.path.isdir(i):
961 shutil.rmtree(i)
962 else:
963 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700964
965
966class PasswordManager(object):
967 def __init__(self):
968 self.editor = os.getenv("EDITOR", None)
969 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
970
971 def GetPasswords(self, items):
972 """Get passwords corresponding to each string in 'items',
973 returning a dict. (The dict may have keys in addition to the
974 values in 'items'.)
975
976 Uses the passwords in $ANDROID_PW_FILE if available, letting the
977 user edit that file to add more needed passwords. If no editor is
978 available, or $ANDROID_PW_FILE isn't define, prompts the user
979 interactively in the ordinary way.
980 """
981
982 current = self.ReadFile()
983
984 first = True
985 while True:
986 missing = []
987 for i in items:
988 if i not in current or not current[i]:
989 missing.append(i)
990 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700991 if not missing:
992 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700993
994 for i in missing:
995 current[i] = ""
996
997 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800998 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700999 answer = raw_input("try to edit again? [y]> ").strip()
1000 if answer and answer[0] not in 'yY':
1001 raise RuntimeError("key passwords unavailable")
1002 first = False
1003
1004 current = self.UpdateAndReadFile(current)
1005
Dan Albert8b72aef2015-03-23 19:13:21 -07001006 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001007 """Prompt the user to enter a value (password) for each key in
1008 'current' whose value is fales. Returns a new dict with all the
1009 values.
1010 """
1011 result = {}
1012 for k, v in sorted(current.iteritems()):
1013 if v:
1014 result[k] = v
1015 else:
1016 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001017 result[k] = getpass.getpass(
1018 "Enter password for %s key> " % k).strip()
1019 if result[k]:
1020 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001021 return result
1022
1023 def UpdateAndReadFile(self, current):
1024 if not self.editor or not self.pwfile:
1025 return self.PromptResult(current)
1026
1027 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001028 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001029 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1030 f.write("# (Additional spaces are harmless.)\n\n")
1031
1032 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001033 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1034 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001035 f.write("[[[ %s ]]] %s\n" % (v, k))
1036 if not v and first_line is None:
1037 # position cursor on first line with no password.
1038 first_line = i + 4
1039 f.close()
1040
1041 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1042 _, _ = p.communicate()
1043
1044 return self.ReadFile()
1045
1046 def ReadFile(self):
1047 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001048 if self.pwfile is None:
1049 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001050 try:
1051 f = open(self.pwfile, "r")
1052 for line in f:
1053 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001054 if not line or line[0] == '#':
1055 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001056 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1057 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001058 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001059 else:
1060 result[m.group(2)] = m.group(1)
1061 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001062 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001063 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001064 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001065 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001066
1067
Dan Albert8e0178d2015-01-27 15:53:15 -08001068def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1069 compress_type=None):
1070 import datetime
1071
1072 # http://b/18015246
1073 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1074 # for files larger than 2GiB. We can work around this by adjusting their
1075 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1076 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1077 # it isn't clear to me exactly what circumstances cause this).
1078 # `zipfile.write()` must be used directly to work around this.
1079 #
1080 # This mess can be avoided if we port to python3.
1081 saved_zip64_limit = zipfile.ZIP64_LIMIT
1082 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1083
1084 if compress_type is None:
1085 compress_type = zip_file.compression
1086 if arcname is None:
1087 arcname = filename
1088
1089 saved_stat = os.stat(filename)
1090
1091 try:
1092 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1093 # file to be zipped and reset it when we're done.
1094 os.chmod(filename, perms)
1095
1096 # Use a fixed timestamp so the output is repeatable.
1097 epoch = datetime.datetime.fromtimestamp(0)
1098 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1099 os.utime(filename, (timestamp, timestamp))
1100
1101 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1102 finally:
1103 os.chmod(filename, saved_stat.st_mode)
1104 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1105 zipfile.ZIP64_LIMIT = saved_zip64_limit
1106
1107
Tao Bao58c1b962015-05-20 09:32:18 -07001108def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001109 compress_type=None):
1110 """Wrap zipfile.writestr() function to work around the zip64 limit.
1111
1112 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1113 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1114 when calling crc32(bytes).
1115
1116 But it still works fine to write a shorter string into a large zip file.
1117 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1118 when we know the string won't be too long.
1119 """
1120
1121 saved_zip64_limit = zipfile.ZIP64_LIMIT
1122 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1123
1124 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1125 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001126 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001127 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001128 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001129 else:
Tao Baof3282b42015-04-01 11:21:55 -07001130 zinfo = zinfo_or_arcname
1131
1132 # If compress_type is given, it overrides the value in zinfo.
1133 if compress_type is not None:
1134 zinfo.compress_type = compress_type
1135
Tao Bao58c1b962015-05-20 09:32:18 -07001136 # If perms is given, it has a priority.
1137 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001138 # If perms doesn't set the file type, mark it as a regular file.
1139 if perms & 0o770000 == 0:
1140 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001141 zinfo.external_attr = perms << 16
1142
Tao Baof3282b42015-04-01 11:21:55 -07001143 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001144 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1145
Dan Albert8b72aef2015-03-23 19:13:21 -07001146 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001147 zipfile.ZIP64_LIMIT = saved_zip64_limit
1148
1149
Tao Bao89d7ab22017-12-14 17:05:33 -08001150def ZipDelete(zip_filename, entries):
1151 """Deletes entries from a ZIP file.
1152
1153 Since deleting entries from a ZIP file is not supported, it shells out to
1154 'zip -d'.
1155
1156 Args:
1157 zip_filename: The name of the ZIP file.
1158 entries: The name of the entry, or the list of names to be deleted.
1159
1160 Raises:
1161 AssertionError: In case of non-zero return from 'zip'.
1162 """
1163 if isinstance(entries, basestring):
1164 entries = [entries]
1165 cmd = ["zip", "-d", zip_filename] + entries
1166 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1167 stdoutdata, _ = proc.communicate()
1168 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1169 stdoutdata)
1170
1171
Tao Baof3282b42015-04-01 11:21:55 -07001172def ZipClose(zip_file):
1173 # http://b/18015246
1174 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1175 # central directory.
1176 saved_zip64_limit = zipfile.ZIP64_LIMIT
1177 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1178
1179 zip_file.close()
1180
1181 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001182
1183
1184class DeviceSpecificParams(object):
1185 module = None
1186 def __init__(self, **kwargs):
1187 """Keyword arguments to the constructor become attributes of this
1188 object, which is passed to all functions in the device-specific
1189 module."""
1190 for k, v in kwargs.iteritems():
1191 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001192 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001193
1194 if self.module is None:
1195 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001196 if not path:
1197 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001198 try:
1199 if os.path.isdir(path):
1200 info = imp.find_module("releasetools", [path])
1201 else:
1202 d, f = os.path.split(path)
1203 b, x = os.path.splitext(f)
1204 if x == ".py":
1205 f = b
1206 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001207 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001208 self.module = imp.load_module("device_specific", *info)
1209 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001210 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001211
1212 def _DoCall(self, function_name, *args, **kwargs):
1213 """Call the named function in the device-specific module, passing
1214 the given args and kwargs. The first argument to the call will be
1215 the DeviceSpecific object itself. If there is no module, or the
1216 module does not define the function, return the value of the
1217 'default' kwarg (which itself defaults to None)."""
1218 if self.module is None or not hasattr(self.module, function_name):
1219 return kwargs.get("default", None)
1220 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1221
1222 def FullOTA_Assertions(self):
1223 """Called after emitting the block of assertions at the top of a
1224 full OTA package. Implementations can add whatever additional
1225 assertions they like."""
1226 return self._DoCall("FullOTA_Assertions")
1227
Doug Zongkere5ff5902012-01-17 10:55:37 -08001228 def FullOTA_InstallBegin(self):
1229 """Called at the start of full OTA installation."""
1230 return self._DoCall("FullOTA_InstallBegin")
1231
Doug Zongker05d3dea2009-06-22 11:32:31 -07001232 def FullOTA_InstallEnd(self):
1233 """Called at the end of full OTA installation; typically this is
1234 used to install the image for the device's baseband processor."""
1235 return self._DoCall("FullOTA_InstallEnd")
1236
1237 def IncrementalOTA_Assertions(self):
1238 """Called after emitting the block of assertions at the top of an
1239 incremental OTA package. Implementations can add whatever
1240 additional assertions they like."""
1241 return self._DoCall("IncrementalOTA_Assertions")
1242
Doug Zongkere5ff5902012-01-17 10:55:37 -08001243 def IncrementalOTA_VerifyBegin(self):
1244 """Called at the start of the verification phase of incremental
1245 OTA installation; additional checks can be placed here to abort
1246 the script before any changes are made."""
1247 return self._DoCall("IncrementalOTA_VerifyBegin")
1248
Doug Zongker05d3dea2009-06-22 11:32:31 -07001249 def IncrementalOTA_VerifyEnd(self):
1250 """Called at the end of the verification phase of incremental OTA
1251 installation; additional checks can be placed here to abort the
1252 script before any changes are made."""
1253 return self._DoCall("IncrementalOTA_VerifyEnd")
1254
Doug Zongkere5ff5902012-01-17 10:55:37 -08001255 def IncrementalOTA_InstallBegin(self):
1256 """Called at the start of incremental OTA installation (after
1257 verification is complete)."""
1258 return self._DoCall("IncrementalOTA_InstallBegin")
1259
Doug Zongker05d3dea2009-06-22 11:32:31 -07001260 def IncrementalOTA_InstallEnd(self):
1261 """Called at the end of incremental OTA installation; typically
1262 this is used to install the image for the device's baseband
1263 processor."""
1264 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001265
Tao Bao9bc6bb22015-11-09 16:58:28 -08001266 def VerifyOTA_Assertions(self):
1267 return self._DoCall("VerifyOTA_Assertions")
1268
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001269class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001270 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001271 self.name = name
1272 self.data = data
1273 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001274 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001275 self.sha1 = sha1(data).hexdigest()
1276
1277 @classmethod
1278 def FromLocalFile(cls, name, diskname):
1279 f = open(diskname, "rb")
1280 data = f.read()
1281 f.close()
1282 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001283
1284 def WriteToTemp(self):
1285 t = tempfile.NamedTemporaryFile()
1286 t.write(self.data)
1287 t.flush()
1288 return t
1289
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001290 def WriteToDir(self, d):
1291 with open(os.path.join(d, self.name), "wb") as fp:
1292 fp.write(self.data)
1293
Geremy Condra36bd3652014-02-06 19:45:10 -08001294 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001295 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001296
1297DIFF_PROGRAM_BY_EXT = {
1298 ".gz" : "imgdiff",
1299 ".zip" : ["imgdiff", "-z"],
1300 ".jar" : ["imgdiff", "-z"],
1301 ".apk" : ["imgdiff", "-z"],
1302 ".img" : "imgdiff",
1303 }
1304
1305class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001306 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001307 self.tf = tf
1308 self.sf = sf
1309 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001310 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001311
1312 def ComputePatch(self):
1313 """Compute the patch (as a string of data) needed to turn sf into
1314 tf. Returns the same tuple as GetPatch()."""
1315
1316 tf = self.tf
1317 sf = self.sf
1318
Doug Zongker24cd2802012-08-14 16:36:15 -07001319 if self.diff_program:
1320 diff_program = self.diff_program
1321 else:
1322 ext = os.path.splitext(tf.name)[1]
1323 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001324
1325 ttemp = tf.WriteToTemp()
1326 stemp = sf.WriteToTemp()
1327
1328 ext = os.path.splitext(tf.name)[1]
1329
1330 try:
1331 ptemp = tempfile.NamedTemporaryFile()
1332 if isinstance(diff_program, list):
1333 cmd = copy.copy(diff_program)
1334 else:
1335 cmd = [diff_program]
1336 cmd.append(stemp.name)
1337 cmd.append(ttemp.name)
1338 cmd.append(ptemp.name)
1339 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001340 err = []
1341 def run():
1342 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001343 if e:
1344 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001345 th = threading.Thread(target=run)
1346 th.start()
1347 th.join(timeout=300) # 5 mins
1348 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001349 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001350 p.terminate()
1351 th.join(5)
1352 if th.is_alive():
1353 p.kill()
1354 th.join()
1355
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001356 if err or p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001357 print("WARNING: failure running %s:\n%s\n" % (
1358 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001359 self.patch = None
1360 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001361 diff = ptemp.read()
1362 finally:
1363 ptemp.close()
1364 stemp.close()
1365 ttemp.close()
1366
1367 self.patch = diff
1368 return self.tf, self.sf, self.patch
1369
1370
1371 def GetPatch(self):
1372 """Return a tuple (target_file, source_file, patch_data).
1373 patch_data may be None if ComputePatch hasn't been called, or if
1374 computing the patch failed."""
1375 return self.tf, self.sf, self.patch
1376
1377
1378def ComputeDifferences(diffs):
1379 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001380 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001381
1382 # Do the largest files first, to try and reduce the long-pole effect.
1383 by_size = [(i.tf.size, i) for i in diffs]
1384 by_size.sort(reverse=True)
1385 by_size = [i[1] for i in by_size]
1386
1387 lock = threading.Lock()
1388 diff_iter = iter(by_size) # accessed under lock
1389
1390 def worker():
1391 try:
1392 lock.acquire()
1393 for d in diff_iter:
1394 lock.release()
1395 start = time.time()
1396 d.ComputePatch()
1397 dur = time.time() - start
1398 lock.acquire()
1399
1400 tf, sf, patch = d.GetPatch()
1401 if sf.name == tf.name:
1402 name = tf.name
1403 else:
1404 name = "%s (%s)" % (tf.name, sf.name)
1405 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001406 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001407 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001408 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1409 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001410 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001411 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001412 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001413 raise
1414
1415 # start worker threads; wait for them all to finish.
1416 threads = [threading.Thread(target=worker)
1417 for i in range(OPTIONS.worker_threads)]
1418 for th in threads:
1419 th.start()
1420 while threads:
1421 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001422
1423
Dan Albert8b72aef2015-03-23 19:13:21 -07001424class BlockDifference(object):
1425 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001426 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001427 self.tgt = tgt
1428 self.src = src
1429 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001430 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001431 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001432
Tao Baodd2a5892015-03-12 12:32:37 -07001433 if version is None:
1434 version = 1
1435 if OPTIONS.info_dict:
1436 version = max(
1437 int(i) for i in
1438 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001439 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001440 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001441
1442 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001443 version=self.version,
1444 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001445 tmpdir = tempfile.mkdtemp()
1446 OPTIONS.tempfiles.append(tmpdir)
1447 self.path = os.path.join(tmpdir, partition)
1448 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001449 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001450 self.touched_src_ranges = b.touched_src_ranges
1451 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001452
Tao Baoaac4ad52015-10-16 15:26:34 -07001453 if src is None:
1454 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1455 else:
1456 _, self.device = GetTypeAndDevice("/" + partition,
1457 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001458
Tao Baod8d14be2016-02-04 14:26:02 -08001459 @property
1460 def required_cache(self):
1461 return self._required_cache
1462
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001463 def WriteScript(self, script, output_zip, progress=None):
1464 if not self.src:
1465 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001466 script.Print("Patching %s image unconditionally..." % (self.partition,))
1467 else:
1468 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001469
Dan Albert8b72aef2015-03-23 19:13:21 -07001470 if progress:
1471 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001472 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001473 if OPTIONS.verify:
1474 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001475
Tao Bao9bc6bb22015-11-09 16:58:28 -08001476 def WriteStrictVerifyScript(self, script):
1477 """Verify all the blocks in the care_map, including clobbered blocks.
1478
1479 This differs from the WriteVerifyScript() function: a) it prints different
1480 error messages; b) it doesn't allow half-way updated images to pass the
1481 verification."""
1482
1483 partition = self.partition
1484 script.Print("Verifying %s..." % (partition,))
1485 ranges = self.tgt.care_map
1486 ranges_str = ranges.to_string_raw()
1487 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1488 'ui_print(" Verified.") || '
1489 'ui_print("\\"%s\\" has unexpected contents.");' % (
1490 self.device, ranges_str,
1491 self.tgt.TotalSha1(include_clobbered_blocks=True),
1492 self.device))
1493 script.AppendExtra("")
1494
Tao Baod522bdc2016-04-12 15:53:16 -07001495 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001496 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001497
1498 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001499 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001500 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001501
1502 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001503 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001504 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001505 ranges = self.touched_src_ranges
1506 expected_sha1 = self.touched_src_sha1
1507 else:
1508 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1509 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001510
1511 # No blocks to be checked, skipping.
1512 if not ranges:
1513 return
1514
Tao Bao5ece99d2015-05-12 11:42:31 -07001515 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001516 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1517 'block_image_verify("%s", '
1518 'package_extract_file("%s.transfer.list"), '
1519 '"%s.new.dat", "%s.patch.dat")) then') % (
1520 self.device, ranges_str, expected_sha1,
1521 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001522 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001523 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001524
Tianjie Xufc3422a2015-12-15 11:53:59 -08001525 if self.version >= 4:
1526
1527 # Bug: 21124327
1528 # When generating incrementals for the system and vendor partitions in
1529 # version 4 or newer, explicitly check the first block (which contains
1530 # the superblock) of the partition to see if it's what we expect. If
1531 # this check fails, give an explicit log message about the partition
1532 # having been remounted R/W (the most likely explanation).
1533 if self.check_first_block:
1534 script.AppendExtra('check_first_block("%s");' % (self.device,))
1535
1536 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001537 if partition == "system":
1538 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1539 else:
1540 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001541 script.AppendExtra((
1542 'ifelse (block_image_recover("{device}", "{ranges}") && '
1543 'block_image_verify("{device}", '
1544 'package_extract_file("{partition}.transfer.list"), '
1545 '"{partition}.new.dat", "{partition}.patch.dat"), '
1546 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001547 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001548 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001549 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001550
Tao Baodd2a5892015-03-12 12:32:37 -07001551 # Abort the OTA update. Note that the incremental OTA cannot be applied
1552 # even if it may match the checksum of the target partition.
1553 # a) If version < 3, operations like move and erase will make changes
1554 # unconditionally and damage the partition.
1555 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001556 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001557 if partition == "system":
1558 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1559 else:
1560 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1561 script.AppendExtra((
1562 'abort("E%d: %s partition has unexpected contents");\n'
1563 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001564
Tao Bao5fcaaef2015-06-01 13:40:49 -07001565 def _WritePostInstallVerifyScript(self, script):
1566 partition = self.partition
1567 script.Print('Verifying the updated %s image...' % (partition,))
1568 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1569 ranges = self.tgt.care_map
1570 ranges_str = ranges.to_string_raw()
1571 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1572 self.device, ranges_str,
1573 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001574
1575 # Bug: 20881595
1576 # Verify that extended blocks are really zeroed out.
1577 if self.tgt.extended:
1578 ranges_str = self.tgt.extended.to_string_raw()
1579 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1580 self.device, ranges_str,
1581 self._HashZeroBlocks(self.tgt.extended.size())))
1582 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001583 if partition == "system":
1584 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1585 else:
1586 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001587 script.AppendExtra(
1588 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001589 ' abort("E%d: %s partition has unexpected non-zero contents after '
1590 'OTA update");\n'
1591 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001592 else:
1593 script.Print('Verified the updated %s image.' % (partition,))
1594
Tianjie Xu209db462016-05-24 17:34:52 -07001595 if partition == "system":
1596 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1597 else:
1598 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1599
Tao Bao5fcaaef2015-06-01 13:40:49 -07001600 script.AppendExtra(
1601 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001602 ' abort("E%d: %s partition has unexpected contents after OTA '
1603 'update");\n'
1604 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001605
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001606 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001607 ZipWrite(output_zip,
1608 '{}.transfer.list'.format(self.path),
1609 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001610
1611 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1612 # almost triples the compression time but doesn't further reduce the size too much.
1613 # For a typical 1.8G system.new.dat
1614 # zip | brotli(quality 6) | brotli(quality 9)
1615 # compressed_size: 942M | 869M (~8% reduced) | 854M
1616 # compression_time: 75s | 265s | 719s
1617 # decompression_time: 15s | 25s | 25s
1618
1619 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001620 brotli_cmd = ['brotli', '--quality=6',
1621 '--output={}.new.dat.br'.format(self.path),
1622 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001623 print("Compressing {}.new.dat with brotli".format(self.partition))
Alex Deymob10e07a2017-11-09 23:53:42 +01001624 p = Run(brotli_cmd, stdout=subprocess.PIPE)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001625 p.communicate()
1626 assert p.returncode == 0,\
1627 'compression of {}.new.dat failed'.format(self.partition)
1628
1629 new_data_name = '{}.new.dat.br'.format(self.partition)
1630 ZipWrite(output_zip,
1631 '{}.new.dat.br'.format(self.path),
1632 new_data_name,
1633 compress_type=zipfile.ZIP_STORED)
1634 else:
1635 new_data_name = '{}.new.dat'.format(self.partition)
1636 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1637
Dan Albert8e0178d2015-01-27 15:53:15 -08001638 ZipWrite(output_zip,
1639 '{}.patch.dat'.format(self.path),
1640 '{}.patch.dat'.format(self.partition),
1641 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001642
Tianjie Xu209db462016-05-24 17:34:52 -07001643 if self.partition == "system":
1644 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1645 else:
1646 code = ErrorCode.VENDOR_UPDATE_FAILURE
1647
Dan Albert8e0178d2015-01-27 15:53:15 -08001648 call = ('block_image_update("{device}", '
1649 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001650 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001651 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001652 device=self.device, partition=self.partition,
1653 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001654 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001655
Dan Albert8b72aef2015-03-23 19:13:21 -07001656 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001657 data = source.ReadRangeSet(ranges)
1658 ctx = sha1()
1659
1660 for p in data:
1661 ctx.update(p)
1662
1663 return ctx.hexdigest()
1664
Tao Baoe9b61912015-07-09 17:37:49 -07001665 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1666 """Return the hash value for all zero blocks."""
1667 zero_block = '\x00' * 4096
1668 ctx = sha1()
1669 for _ in range(num_blocks):
1670 ctx.update(zero_block)
1671
1672 return ctx.hexdigest()
1673
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001674
1675DataImage = blockimgdiff.DataImage
1676
Doug Zongker96a57e72010-09-26 14:57:41 -07001677# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001678PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001679 "ext4": "EMMC",
1680 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001681 "f2fs": "EMMC",
1682 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001683}
Doug Zongker96a57e72010-09-26 14:57:41 -07001684
1685def GetTypeAndDevice(mount_point, info):
1686 fstab = info["fstab"]
1687 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001688 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1689 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001690 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001691 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001692
1693
1694def ParseCertificate(data):
1695 """Parse a PEM-format certificate."""
1696 cert = []
1697 save = False
1698 for line in data.split("\n"):
1699 if "--END CERTIFICATE--" in line:
1700 break
1701 if save:
1702 cert.append(line)
1703 if "--BEGIN CERTIFICATE--" in line:
1704 save = True
1705 cert = "".join(cert).decode('base64')
1706 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001707
Doug Zongker412c02f2014-02-13 10:58:24 -08001708def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1709 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001710 """Generate a binary patch that creates the recovery image starting
1711 with the boot image. (Most of the space in these images is just the
1712 kernel, which is identical for the two, so the resulting patch
1713 should be efficient.) Add it to the output zip, along with a shell
1714 script that is run from init.rc on first boot to actually do the
1715 patching and install the new recovery image.
1716
1717 recovery_img and boot_img should be File objects for the
1718 corresponding images. info should be the dictionary returned by
1719 common.LoadInfoDict() on the input target_files.
1720 """
1721
Doug Zongker412c02f2014-02-13 10:58:24 -08001722 if info_dict is None:
1723 info_dict = OPTIONS.info_dict
1724
Tao Baof2cffbd2015-07-22 12:33:18 -07001725 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001726
Tao Baof2cffbd2015-07-22 12:33:18 -07001727 if full_recovery_image:
1728 output_sink("etc/recovery.img", recovery_img.data)
1729
1730 else:
1731 diff_program = ["imgdiff"]
1732 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1733 if os.path.exists(path):
1734 diff_program.append("-b")
1735 diff_program.append(path)
1736 bonus_args = "-b /system/etc/recovery-resource.dat"
1737 else:
1738 bonus_args = ""
1739
1740 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1741 _, _, patch = d.ComputePatch()
1742 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001743
Dan Albertebb19aa2015-03-27 19:11:53 -07001744 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001745 # The following GetTypeAndDevice()s need to use the path in the target
1746 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001747 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1748 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1749 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001750 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001751
Tao Baof2cffbd2015-07-22 12:33:18 -07001752 if full_recovery_image:
1753 sh = """#!/system/bin/sh
1754if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1755 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"
1756else
1757 log -t recovery "Recovery image already installed"
1758fi
1759""" % {'type': recovery_type,
1760 'device': recovery_device,
1761 'sha1': recovery_img.sha1,
1762 'size': recovery_img.size}
1763 else:
1764 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001765if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1766 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"
1767else
1768 log -t recovery "Recovery image already installed"
1769fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001770""" % {'boot_size': boot_img.size,
1771 'boot_sha1': boot_img.sha1,
1772 'recovery_size': recovery_img.size,
1773 'recovery_sha1': recovery_img.sha1,
1774 'boot_type': boot_type,
1775 'boot_device': boot_device,
1776 'recovery_type': recovery_type,
1777 'recovery_device': recovery_device,
1778 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001779
1780 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001781 # in the L release.
1782 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001783
Tao Bao89fbb0f2017-01-10 10:47:58 -08001784 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001785
1786 output_sink(sh_location, sh)