blob: 03e808f95a204ad2f391dc0965568135f376242a [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 Baobf70c3182017-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 Baobf70c3182017-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 Baobf70c3182017-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 Baobf70c3182017-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 Baobf70c3182017-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 Baobf70c3182017-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):
Tao Bao1c830bf2017-12-25 10:43:47 -0800577 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800578
Tao Bao1c830bf2017-12-25 10:43:47 -0800579 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
580 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800581
Tao Bao1c830bf2017-12-25 10:43:47 -0800582 Returns:
583 (tempdir, zipobj): tempdir is the name of the temprary directory; zipobj is
584 a zipfile.ZipFile (of the main file), open for reading.
Doug Zongker55d93282011-01-25 17:03:34 -0800585 """
Doug Zongkereef39442009-04-02 12:14:19 -0700586
Doug Zongker55d93282011-01-25 17:03:34 -0800587 def unzip_to_dir(filename, dirname):
588 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
589 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800590 cmd.extend(pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800591 p = Run(cmd, stdout=subprocess.PIPE)
592 p.communicate()
593 if p.returncode != 0:
594 raise ExternalError("failed to unzip input target-files \"%s\"" %
595 (filename,))
596
Tao Bao1c830bf2017-12-25 10:43:47 -0800597 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800598 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
599 if m:
600 unzip_to_dir(m.group(1), tmp)
601 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
602 filename = m.group(1)
603 else:
604 unzip_to_dir(filename, tmp)
605
606 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700607
608
609def GetKeyPasswords(keylist):
610 """Given a list of keys, prompt the user to enter passwords for
611 those which require them. Return a {key: password} dict. password
612 will be None if the key has no password."""
613
Doug Zongker8ce7c252009-05-22 13:34:54 -0700614 no_passwords = []
615 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700616 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700617 devnull = open("/dev/null", "w+b")
618 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800619 # We don't need a password for things that aren't really keys.
620 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700621 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700622 continue
623
T.R. Fullhart37e10522013-03-18 10:31:26 -0700624 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700625 "-inform", "DER", "-nocrypt"],
626 stdin=devnull.fileno(),
627 stdout=devnull.fileno(),
628 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700629 p.communicate()
630 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700631 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700632 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700633 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700634 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
635 "-inform", "DER", "-passin", "pass:"],
636 stdin=devnull.fileno(),
637 stdout=devnull.fileno(),
638 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700639 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700640 if p.returncode == 0:
641 # Encrypted key with empty string as password.
642 key_passwords[k] = ''
643 elif stderr.startswith('Error decrypting key'):
644 # Definitely encrypted key.
645 # It would have said "Error reading key" if it didn't parse correctly.
646 need_passwords.append(k)
647 else:
648 # Potentially, a type of key that openssl doesn't understand.
649 # We'll let the routines in signapk.jar handle it.
650 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700651 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700652
T.R. Fullhart37e10522013-03-18 10:31:26 -0700653 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700654 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700655 return key_passwords
656
657
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800658def GetMinSdkVersion(apk_name):
659 """Get the minSdkVersion delared in the APK. This can be both a decimal number
660 (API Level) or a codename.
661 """
662
663 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
664 output, err = p.communicate()
665 if err:
666 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
667 % (p.returncode,))
668
669 for line in output.split("\n"):
670 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
671 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
672 if m:
673 return m.group(1)
674 raise ExternalError("No minSdkVersion returned by aapt")
675
676
677def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
678 """Get the minSdkVersion declared in the APK as a number (API Level). If
679 minSdkVersion is set to a codename, it is translated to a number using the
680 provided map.
681 """
682
683 version = GetMinSdkVersion(apk_name)
684 try:
685 return int(version)
686 except ValueError:
687 # Not a decimal number. Codename?
688 if version in codename_to_api_level_map:
689 return codename_to_api_level_map[version]
690 else:
691 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
692 % (version, codename_to_api_level_map))
693
694
695def SignFile(input_name, output_name, key, password, min_api_level=None,
696 codename_to_api_level_map=dict(),
697 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700698 """Sign the input_name zip/jar/apk, producing output_name. Use the
699 given key and password (the latter may be None if the key does not
700 have a password.
701
Doug Zongker951495f2009-08-14 12:44:19 -0700702 If whole_file is true, use the "-w" option to SignApk to embed a
703 signature that covers the whole file in the archive comment of the
704 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800705
706 min_api_level is the API Level (int) of the oldest platform this file may end
707 up on. If not specified for an APK, the API Level is obtained by interpreting
708 the minSdkVersion attribute of the APK's AndroidManifest.xml.
709
710 codename_to_api_level_map is needed to translate the codename which may be
711 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700712 """
Doug Zongker951495f2009-08-14 12:44:19 -0700713
Alex Klyubin9667b182015-12-10 13:38:50 -0800714 java_library_path = os.path.join(
715 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
716
Tao Baoe95540e2016-11-08 12:08:53 -0800717 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
718 ["-Djava.library.path=" + java_library_path,
719 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
720 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700721 if whole_file:
722 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800723
724 min_sdk_version = min_api_level
725 if min_sdk_version is None:
726 if not whole_file:
727 min_sdk_version = GetMinSdkVersionInt(
728 input_name, codename_to_api_level_map)
729 if min_sdk_version is not None:
730 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
731
T.R. Fullhart37e10522013-03-18 10:31:26 -0700732 cmd.extend([key + OPTIONS.public_key_suffix,
733 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800734 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700735
736 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700737 if password is not None:
738 password += "\n"
739 p.communicate(password)
740 if p.returncode != 0:
741 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
742
Doug Zongkereef39442009-04-02 12:14:19 -0700743
Doug Zongker37974732010-09-16 17:44:38 -0700744def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800745 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700746
Tao Bao9dd909e2017-11-14 11:27:32 -0800747 For non-AVB images, raise exception if the data is too big. Print a warning
748 if the data is nearing the maximum size.
749
750 For AVB images, the actual image size should be identical to the limit.
751
752 Args:
753 data: A string that contains all the data for the partition.
754 target: The partition name. The ".img" suffix is optional.
755 info_dict: The dict to be looked up for relevant info.
756 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700757 if target.endswith(".img"):
758 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700759 mount_point = "/" + target
760
Ying Wangf8824af2014-06-03 14:07:27 -0700761 fs_type = None
762 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700763 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700764 if mount_point == "/userdata":
765 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700766 p = info_dict["fstab"][mount_point]
767 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800768 device = p.device
769 if "/" in device:
770 device = device[device.rfind("/")+1:]
771 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700772 if not fs_type or not limit:
773 return
Doug Zongkereef39442009-04-02 12:14:19 -0700774
Andrew Boie0f9aec82012-02-14 09:32:52 -0800775 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800776 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
777 # path.
778 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
779 if size != limit:
780 raise ExternalError(
781 "Mismatching image size for %s: expected %d actual %d" % (
782 target, limit, size))
783 else:
784 pct = float(size) * 100.0 / limit
785 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
786 if pct >= 99.0:
787 raise ExternalError(msg)
788 elif pct >= 95.0:
789 print("\n WARNING: %s\n" % (msg,))
790 elif OPTIONS.verbose:
791 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700792
793
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800794def ReadApkCerts(tf_zip):
795 """Given a target_files ZipFile, parse the META/apkcerts.txt file
Narayan Kamatha07bf042017-08-14 14:49:21 +0100796 and return a tuple with the following elements: (1) a dictionary that maps
797 packages to certs (based on the "certificate" and "private_key" attributes
798 in the file. (2) A string representing the extension of compressed APKs in
799 the target files (e.g ".gz" ".bro")."""
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800800 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100801 compressed_extension = None
802
Tao Bao0f990332017-09-08 19:02:54 -0700803 # META/apkcerts.txt contains the info for _all_ the packages known at build
804 # time. Filter out the ones that are not installed.
805 installed_files = set()
806 for name in tf_zip.namelist():
807 basename = os.path.basename(name)
808 if basename:
809 installed_files.add(basename)
810
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800811 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
812 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700813 if not line:
814 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100815 m = re.match(r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
816 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
817 line)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800818 if m:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100819 matches = m.groupdict()
820 cert = matches["CERT"]
821 privkey = matches["PRIVKEY"]
822 name = matches["NAME"]
823 this_compressed_extension = matches["COMPRESSED"]
T.R. Fullhart37e10522013-03-18 10:31:26 -0700824 public_key_suffix_len = len(OPTIONS.public_key_suffix)
825 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800826 if cert in SPECIAL_CERT_STRINGS and not privkey:
827 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700828 elif (cert.endswith(OPTIONS.public_key_suffix) and
829 privkey.endswith(OPTIONS.private_key_suffix) and
830 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
831 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800832 else:
833 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
Narayan Kamatha07bf042017-08-14 14:49:21 +0100834 if this_compressed_extension:
Tao Bao0f990332017-09-08 19:02:54 -0700835 # Only count the installed files.
836 filename = name + '.' + this_compressed_extension
837 if filename not in installed_files:
838 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100839 # Make sure that all the values in the compression map have the same
840 # extension. We don't support multiple compression methods in the same
841 # system image.
842 if compressed_extension:
843 if this_compressed_extension != compressed_extension:
844 raise ValueError("multiple compressed extensions : %s vs %s",
845 (compressed_extension, this_compressed_extension))
846 else:
847 compressed_extension = this_compressed_extension
848
849 return (certmap, ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800850
851
Doug Zongkereef39442009-04-02 12:14:19 -0700852COMMON_DOCSTRING = """
853 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700854 Prepend <dir>/bin to the list of places to search for binaries
855 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700856
Doug Zongker05d3dea2009-06-22 11:32:31 -0700857 -s (--device_specific) <file>
858 Path to the python module containing device-specific
859 releasetools code.
860
Doug Zongker8bec09e2009-11-30 15:37:14 -0800861 -x (--extra) <key=value>
862 Add a key/value pair to the 'extras' dict, which device-specific
863 extension code may look at.
864
Doug Zongkereef39442009-04-02 12:14:19 -0700865 -v (--verbose)
866 Show command lines being executed.
867
868 -h (--help)
869 Display this usage message and exit.
870"""
871
872def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800873 print(docstring.rstrip("\n"))
874 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700875
876
877def ParseOptions(argv,
878 docstring,
879 extra_opts="", extra_long_opts=(),
880 extra_option_handler=None):
881 """Parse the options in argv and return any arguments that aren't
882 flags. docstring is the calling module's docstring, to be displayed
883 for errors and -h. extra_opts and extra_long_opts are for flags
884 defined by the caller, which are processed by passing them to
885 extra_option_handler."""
886
887 try:
888 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800889 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800890 ["help", "verbose", "path=", "signapk_path=",
891 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700892 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700893 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
894 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800895 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700896 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700897 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700898 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800899 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700900 sys.exit(2)
901
Doug Zongkereef39442009-04-02 12:14:19 -0700902 for o, a in opts:
903 if o in ("-h", "--help"):
904 Usage(docstring)
905 sys.exit()
906 elif o in ("-v", "--verbose"):
907 OPTIONS.verbose = True
908 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700909 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700910 elif o in ("--signapk_path",):
911 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800912 elif o in ("--signapk_shared_library_path",):
913 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700914 elif o in ("--extra_signapk_args",):
915 OPTIONS.extra_signapk_args = shlex.split(a)
916 elif o in ("--java_path",):
917 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700918 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800919 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700920 elif o in ("--public_key_suffix",):
921 OPTIONS.public_key_suffix = a
922 elif o in ("--private_key_suffix",):
923 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800924 elif o in ("--boot_signer_path",):
925 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700926 elif o in ("--boot_signer_args",):
927 OPTIONS.boot_signer_args = shlex.split(a)
928 elif o in ("--verity_signer_path",):
929 OPTIONS.verity_signer_path = a
930 elif o in ("--verity_signer_args",):
931 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700932 elif o in ("-s", "--device_specific"):
933 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800934 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800935 key, value = a.split("=", 1)
936 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700937 else:
938 if extra_option_handler is None or not extra_option_handler(o, a):
939 assert False, "unknown option \"%s\"" % (o,)
940
Doug Zongker85448772014-09-09 14:59:20 -0700941 if OPTIONS.search_path:
942 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
943 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700944
945 return args
946
947
Tao Bao4c851b12016-09-19 13:54:38 -0700948def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700949 """Make a temp file and add it to the list of things to be deleted
950 when Cleanup() is called. Return the filename."""
951 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
952 os.close(fd)
953 OPTIONS.tempfiles.append(fn)
954 return fn
955
956
Tao Bao1c830bf2017-12-25 10:43:47 -0800957def MakeTempDir(prefix='tmp', suffix=''):
958 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
959
960 Returns:
961 The absolute pathname of the new directory.
962 """
963 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
964 OPTIONS.tempfiles.append(dir_name)
965 return dir_name
966
967
Doug Zongkereef39442009-04-02 12:14:19 -0700968def Cleanup():
969 for i in OPTIONS.tempfiles:
970 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -0800971 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700972 else:
973 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -0800974 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -0700975
976
977class PasswordManager(object):
978 def __init__(self):
979 self.editor = os.getenv("EDITOR", None)
980 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
981
982 def GetPasswords(self, items):
983 """Get passwords corresponding to each string in 'items',
984 returning a dict. (The dict may have keys in addition to the
985 values in 'items'.)
986
987 Uses the passwords in $ANDROID_PW_FILE if available, letting the
988 user edit that file to add more needed passwords. If no editor is
989 available, or $ANDROID_PW_FILE isn't define, prompts the user
990 interactively in the ordinary way.
991 """
992
993 current = self.ReadFile()
994
995 first = True
996 while True:
997 missing = []
998 for i in items:
999 if i not in current or not current[i]:
1000 missing.append(i)
1001 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001002 if not missing:
1003 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001004
1005 for i in missing:
1006 current[i] = ""
1007
1008 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001009 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001010 answer = raw_input("try to edit again? [y]> ").strip()
1011 if answer and answer[0] not in 'yY':
1012 raise RuntimeError("key passwords unavailable")
1013 first = False
1014
1015 current = self.UpdateAndReadFile(current)
1016
Dan Albert8b72aef2015-03-23 19:13:21 -07001017 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001018 """Prompt the user to enter a value (password) for each key in
1019 'current' whose value is fales. Returns a new dict with all the
1020 values.
1021 """
1022 result = {}
1023 for k, v in sorted(current.iteritems()):
1024 if v:
1025 result[k] = v
1026 else:
1027 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001028 result[k] = getpass.getpass(
1029 "Enter password for %s key> " % k).strip()
1030 if result[k]:
1031 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001032 return result
1033
1034 def UpdateAndReadFile(self, current):
1035 if not self.editor or not self.pwfile:
1036 return self.PromptResult(current)
1037
1038 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001039 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001040 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1041 f.write("# (Additional spaces are harmless.)\n\n")
1042
1043 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001044 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1045 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001046 f.write("[[[ %s ]]] %s\n" % (v, k))
1047 if not v and first_line is None:
1048 # position cursor on first line with no password.
1049 first_line = i + 4
1050 f.close()
1051
1052 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1053 _, _ = p.communicate()
1054
1055 return self.ReadFile()
1056
1057 def ReadFile(self):
1058 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001059 if self.pwfile is None:
1060 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001061 try:
1062 f = open(self.pwfile, "r")
1063 for line in f:
1064 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001065 if not line or line[0] == '#':
1066 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001067 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1068 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001069 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001070 else:
1071 result[m.group(2)] = m.group(1)
1072 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001073 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001074 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001075 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001076 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001077
1078
Dan Albert8e0178d2015-01-27 15:53:15 -08001079def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1080 compress_type=None):
1081 import datetime
1082
1083 # http://b/18015246
1084 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1085 # for files larger than 2GiB. We can work around this by adjusting their
1086 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1087 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1088 # it isn't clear to me exactly what circumstances cause this).
1089 # `zipfile.write()` must be used directly to work around this.
1090 #
1091 # This mess can be avoided if we port to python3.
1092 saved_zip64_limit = zipfile.ZIP64_LIMIT
1093 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1094
1095 if compress_type is None:
1096 compress_type = zip_file.compression
1097 if arcname is None:
1098 arcname = filename
1099
1100 saved_stat = os.stat(filename)
1101
1102 try:
1103 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1104 # file to be zipped and reset it when we're done.
1105 os.chmod(filename, perms)
1106
1107 # Use a fixed timestamp so the output is repeatable.
1108 epoch = datetime.datetime.fromtimestamp(0)
1109 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1110 os.utime(filename, (timestamp, timestamp))
1111
1112 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1113 finally:
1114 os.chmod(filename, saved_stat.st_mode)
1115 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1116 zipfile.ZIP64_LIMIT = saved_zip64_limit
1117
1118
Tao Bao58c1b962015-05-20 09:32:18 -07001119def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001120 compress_type=None):
1121 """Wrap zipfile.writestr() function to work around the zip64 limit.
1122
1123 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1124 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1125 when calling crc32(bytes).
1126
1127 But it still works fine to write a shorter string into a large zip file.
1128 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1129 when we know the string won't be too long.
1130 """
1131
1132 saved_zip64_limit = zipfile.ZIP64_LIMIT
1133 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1134
1135 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1136 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001137 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001138 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001139 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001140 else:
Tao Baof3282b42015-04-01 11:21:55 -07001141 zinfo = zinfo_or_arcname
1142
1143 # If compress_type is given, it overrides the value in zinfo.
1144 if compress_type is not None:
1145 zinfo.compress_type = compress_type
1146
Tao Bao58c1b962015-05-20 09:32:18 -07001147 # If perms is given, it has a priority.
1148 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001149 # If perms doesn't set the file type, mark it as a regular file.
1150 if perms & 0o770000 == 0:
1151 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001152 zinfo.external_attr = perms << 16
1153
Tao Baof3282b42015-04-01 11:21:55 -07001154 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001155 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1156
Dan Albert8b72aef2015-03-23 19:13:21 -07001157 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001158 zipfile.ZIP64_LIMIT = saved_zip64_limit
1159
1160
Tao Bao89d7ab22017-12-14 17:05:33 -08001161def ZipDelete(zip_filename, entries):
1162 """Deletes entries from a ZIP file.
1163
1164 Since deleting entries from a ZIP file is not supported, it shells out to
1165 'zip -d'.
1166
1167 Args:
1168 zip_filename: The name of the ZIP file.
1169 entries: The name of the entry, or the list of names to be deleted.
1170
1171 Raises:
1172 AssertionError: In case of non-zero return from 'zip'.
1173 """
1174 if isinstance(entries, basestring):
1175 entries = [entries]
1176 cmd = ["zip", "-d", zip_filename] + entries
1177 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1178 stdoutdata, _ = proc.communicate()
1179 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1180 stdoutdata)
1181
1182
Tao Baof3282b42015-04-01 11:21:55 -07001183def ZipClose(zip_file):
1184 # http://b/18015246
1185 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1186 # central directory.
1187 saved_zip64_limit = zipfile.ZIP64_LIMIT
1188 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1189
1190 zip_file.close()
1191
1192 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001193
1194
1195class DeviceSpecificParams(object):
1196 module = None
1197 def __init__(self, **kwargs):
1198 """Keyword arguments to the constructor become attributes of this
1199 object, which is passed to all functions in the device-specific
1200 module."""
1201 for k, v in kwargs.iteritems():
1202 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001203 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001204
1205 if self.module is None:
1206 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001207 if not path:
1208 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001209 try:
1210 if os.path.isdir(path):
1211 info = imp.find_module("releasetools", [path])
1212 else:
1213 d, f = os.path.split(path)
1214 b, x = os.path.splitext(f)
1215 if x == ".py":
1216 f = b
1217 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001218 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001219 self.module = imp.load_module("device_specific", *info)
1220 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001221 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001222
1223 def _DoCall(self, function_name, *args, **kwargs):
1224 """Call the named function in the device-specific module, passing
1225 the given args and kwargs. The first argument to the call will be
1226 the DeviceSpecific object itself. If there is no module, or the
1227 module does not define the function, return the value of the
1228 'default' kwarg (which itself defaults to None)."""
1229 if self.module is None or not hasattr(self.module, function_name):
1230 return kwargs.get("default", None)
1231 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1232
1233 def FullOTA_Assertions(self):
1234 """Called after emitting the block of assertions at the top of a
1235 full OTA package. Implementations can add whatever additional
1236 assertions they like."""
1237 return self._DoCall("FullOTA_Assertions")
1238
Doug Zongkere5ff5902012-01-17 10:55:37 -08001239 def FullOTA_InstallBegin(self):
1240 """Called at the start of full OTA installation."""
1241 return self._DoCall("FullOTA_InstallBegin")
1242
Doug Zongker05d3dea2009-06-22 11:32:31 -07001243 def FullOTA_InstallEnd(self):
1244 """Called at the end of full OTA installation; typically this is
1245 used to install the image for the device's baseband processor."""
1246 return self._DoCall("FullOTA_InstallEnd")
1247
1248 def IncrementalOTA_Assertions(self):
1249 """Called after emitting the block of assertions at the top of an
1250 incremental OTA package. Implementations can add whatever
1251 additional assertions they like."""
1252 return self._DoCall("IncrementalOTA_Assertions")
1253
Doug Zongkere5ff5902012-01-17 10:55:37 -08001254 def IncrementalOTA_VerifyBegin(self):
1255 """Called at the start of the verification phase of incremental
1256 OTA installation; additional checks can be placed here to abort
1257 the script before any changes are made."""
1258 return self._DoCall("IncrementalOTA_VerifyBegin")
1259
Doug Zongker05d3dea2009-06-22 11:32:31 -07001260 def IncrementalOTA_VerifyEnd(self):
1261 """Called at the end of the verification phase of incremental OTA
1262 installation; additional checks can be placed here to abort the
1263 script before any changes are made."""
1264 return self._DoCall("IncrementalOTA_VerifyEnd")
1265
Doug Zongkere5ff5902012-01-17 10:55:37 -08001266 def IncrementalOTA_InstallBegin(self):
1267 """Called at the start of incremental OTA installation (after
1268 verification is complete)."""
1269 return self._DoCall("IncrementalOTA_InstallBegin")
1270
Doug Zongker05d3dea2009-06-22 11:32:31 -07001271 def IncrementalOTA_InstallEnd(self):
1272 """Called at the end of incremental OTA installation; typically
1273 this is used to install the image for the device's baseband
1274 processor."""
1275 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001276
Tao Bao9bc6bb22015-11-09 16:58:28 -08001277 def VerifyOTA_Assertions(self):
1278 return self._DoCall("VerifyOTA_Assertions")
1279
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001280class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001281 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001282 self.name = name
1283 self.data = data
1284 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001285 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001286 self.sha1 = sha1(data).hexdigest()
1287
1288 @classmethod
1289 def FromLocalFile(cls, name, diskname):
1290 f = open(diskname, "rb")
1291 data = f.read()
1292 f.close()
1293 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001294
1295 def WriteToTemp(self):
1296 t = tempfile.NamedTemporaryFile()
1297 t.write(self.data)
1298 t.flush()
1299 return t
1300
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001301 def WriteToDir(self, d):
1302 with open(os.path.join(d, self.name), "wb") as fp:
1303 fp.write(self.data)
1304
Geremy Condra36bd3652014-02-06 19:45:10 -08001305 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001306 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001307
1308DIFF_PROGRAM_BY_EXT = {
1309 ".gz" : "imgdiff",
1310 ".zip" : ["imgdiff", "-z"],
1311 ".jar" : ["imgdiff", "-z"],
1312 ".apk" : ["imgdiff", "-z"],
1313 ".img" : "imgdiff",
1314 }
1315
1316class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001317 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001318 self.tf = tf
1319 self.sf = sf
1320 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001321 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001322
1323 def ComputePatch(self):
1324 """Compute the patch (as a string of data) needed to turn sf into
1325 tf. Returns the same tuple as GetPatch()."""
1326
1327 tf = self.tf
1328 sf = self.sf
1329
Doug Zongker24cd2802012-08-14 16:36:15 -07001330 if self.diff_program:
1331 diff_program = self.diff_program
1332 else:
1333 ext = os.path.splitext(tf.name)[1]
1334 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001335
1336 ttemp = tf.WriteToTemp()
1337 stemp = sf.WriteToTemp()
1338
1339 ext = os.path.splitext(tf.name)[1]
1340
1341 try:
1342 ptemp = tempfile.NamedTemporaryFile()
1343 if isinstance(diff_program, list):
1344 cmd = copy.copy(diff_program)
1345 else:
1346 cmd = [diff_program]
1347 cmd.append(stemp.name)
1348 cmd.append(ttemp.name)
1349 cmd.append(ptemp.name)
1350 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001351 err = []
1352 def run():
1353 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001354 if e:
1355 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001356 th = threading.Thread(target=run)
1357 th.start()
1358 th.join(timeout=300) # 5 mins
1359 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001360 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001361 p.terminate()
1362 th.join(5)
1363 if th.is_alive():
1364 p.kill()
1365 th.join()
1366
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001367 if err or p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001368 print("WARNING: failure running %s:\n%s\n" % (
1369 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001370 self.patch = None
1371 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001372 diff = ptemp.read()
1373 finally:
1374 ptemp.close()
1375 stemp.close()
1376 ttemp.close()
1377
1378 self.patch = diff
1379 return self.tf, self.sf, self.patch
1380
1381
1382 def GetPatch(self):
1383 """Return a tuple (target_file, source_file, patch_data).
1384 patch_data may be None if ComputePatch hasn't been called, or if
1385 computing the patch failed."""
1386 return self.tf, self.sf, self.patch
1387
1388
1389def ComputeDifferences(diffs):
1390 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001391 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001392
1393 # Do the largest files first, to try and reduce the long-pole effect.
1394 by_size = [(i.tf.size, i) for i in diffs]
1395 by_size.sort(reverse=True)
1396 by_size = [i[1] for i in by_size]
1397
1398 lock = threading.Lock()
1399 diff_iter = iter(by_size) # accessed under lock
1400
1401 def worker():
1402 try:
1403 lock.acquire()
1404 for d in diff_iter:
1405 lock.release()
1406 start = time.time()
1407 d.ComputePatch()
1408 dur = time.time() - start
1409 lock.acquire()
1410
1411 tf, sf, patch = d.GetPatch()
1412 if sf.name == tf.name:
1413 name = tf.name
1414 else:
1415 name = "%s (%s)" % (tf.name, sf.name)
1416 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001417 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001418 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001419 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1420 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001421 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001422 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001423 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001424 raise
1425
1426 # start worker threads; wait for them all to finish.
1427 threads = [threading.Thread(target=worker)
1428 for i in range(OPTIONS.worker_threads)]
1429 for th in threads:
1430 th.start()
1431 while threads:
1432 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001433
1434
Dan Albert8b72aef2015-03-23 19:13:21 -07001435class BlockDifference(object):
1436 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001437 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001438 self.tgt = tgt
1439 self.src = src
1440 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001441 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001442 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001443
Tao Baodd2a5892015-03-12 12:32:37 -07001444 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001445 version = max(
1446 int(i) for i in
1447 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001448 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001449 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001450
1451 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001452 version=self.version,
1453 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001454 tmpdir = tempfile.mkdtemp()
1455 OPTIONS.tempfiles.append(tmpdir)
1456 self.path = os.path.join(tmpdir, partition)
1457 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001458 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001459 self.touched_src_ranges = b.touched_src_ranges
1460 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001461
Tao Baoaac4ad52015-10-16 15:26:34 -07001462 if src is None:
1463 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1464 else:
1465 _, self.device = GetTypeAndDevice("/" + partition,
1466 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001467
Tao Baod8d14be2016-02-04 14:26:02 -08001468 @property
1469 def required_cache(self):
1470 return self._required_cache
1471
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001472 def WriteScript(self, script, output_zip, progress=None):
1473 if not self.src:
1474 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001475 script.Print("Patching %s image unconditionally..." % (self.partition,))
1476 else:
1477 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001478
Dan Albert8b72aef2015-03-23 19:13:21 -07001479 if progress:
1480 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001481 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001482 if OPTIONS.verify:
1483 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001484
Tao Bao9bc6bb22015-11-09 16:58:28 -08001485 def WriteStrictVerifyScript(self, script):
1486 """Verify all the blocks in the care_map, including clobbered blocks.
1487
1488 This differs from the WriteVerifyScript() function: a) it prints different
1489 error messages; b) it doesn't allow half-way updated images to pass the
1490 verification."""
1491
1492 partition = self.partition
1493 script.Print("Verifying %s..." % (partition,))
1494 ranges = self.tgt.care_map
1495 ranges_str = ranges.to_string_raw()
1496 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1497 'ui_print(" Verified.") || '
1498 'ui_print("\\"%s\\" has unexpected contents.");' % (
1499 self.device, ranges_str,
1500 self.tgt.TotalSha1(include_clobbered_blocks=True),
1501 self.device))
1502 script.AppendExtra("")
1503
Tao Baod522bdc2016-04-12 15:53:16 -07001504 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001505 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001506
1507 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001508 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001509 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001510
1511 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001512 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001513 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001514 ranges = self.touched_src_ranges
1515 expected_sha1 = self.touched_src_sha1
1516 else:
1517 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1518 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001519
1520 # No blocks to be checked, skipping.
1521 if not ranges:
1522 return
1523
Tao Bao5ece99d2015-05-12 11:42:31 -07001524 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001525 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1526 'block_image_verify("%s", '
1527 'package_extract_file("%s.transfer.list"), '
1528 '"%s.new.dat", "%s.patch.dat")) then') % (
1529 self.device, ranges_str, expected_sha1,
1530 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001531 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001532 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001533
Tianjie Xufc3422a2015-12-15 11:53:59 -08001534 if self.version >= 4:
1535
1536 # Bug: 21124327
1537 # When generating incrementals for the system and vendor partitions in
1538 # version 4 or newer, explicitly check the first block (which contains
1539 # the superblock) of the partition to see if it's what we expect. If
1540 # this check fails, give an explicit log message about the partition
1541 # having been remounted R/W (the most likely explanation).
1542 if self.check_first_block:
1543 script.AppendExtra('check_first_block("%s");' % (self.device,))
1544
1545 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001546 if partition == "system":
1547 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1548 else:
1549 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001550 script.AppendExtra((
1551 'ifelse (block_image_recover("{device}", "{ranges}") && '
1552 'block_image_verify("{device}", '
1553 'package_extract_file("{partition}.transfer.list"), '
1554 '"{partition}.new.dat", "{partition}.patch.dat"), '
1555 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001556 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001557 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001558 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001559
Tao Baodd2a5892015-03-12 12:32:37 -07001560 # Abort the OTA update. Note that the incremental OTA cannot be applied
1561 # even if it may match the checksum of the target partition.
1562 # a) If version < 3, operations like move and erase will make changes
1563 # unconditionally and damage the partition.
1564 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001565 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001566 if partition == "system":
1567 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1568 else:
1569 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1570 script.AppendExtra((
1571 'abort("E%d: %s partition has unexpected contents");\n'
1572 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001573
Tao Bao5fcaaef2015-06-01 13:40:49 -07001574 def _WritePostInstallVerifyScript(self, script):
1575 partition = self.partition
1576 script.Print('Verifying the updated %s image...' % (partition,))
1577 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1578 ranges = self.tgt.care_map
1579 ranges_str = ranges.to_string_raw()
1580 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1581 self.device, ranges_str,
1582 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001583
1584 # Bug: 20881595
1585 # Verify that extended blocks are really zeroed out.
1586 if self.tgt.extended:
1587 ranges_str = self.tgt.extended.to_string_raw()
1588 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1589 self.device, ranges_str,
1590 self._HashZeroBlocks(self.tgt.extended.size())))
1591 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001592 if partition == "system":
1593 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1594 else:
1595 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001596 script.AppendExtra(
1597 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001598 ' abort("E%d: %s partition has unexpected non-zero contents after '
1599 'OTA update");\n'
1600 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001601 else:
1602 script.Print('Verified the updated %s image.' % (partition,))
1603
Tianjie Xu209db462016-05-24 17:34:52 -07001604 if partition == "system":
1605 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1606 else:
1607 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1608
Tao Bao5fcaaef2015-06-01 13:40:49 -07001609 script.AppendExtra(
1610 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001611 ' abort("E%d: %s partition has unexpected contents after OTA '
1612 'update");\n'
1613 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001614
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001615 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001616 ZipWrite(output_zip,
1617 '{}.transfer.list'.format(self.path),
1618 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001619
1620 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1621 # almost triples the compression time but doesn't further reduce the size too much.
1622 # For a typical 1.8G system.new.dat
1623 # zip | brotli(quality 6) | brotli(quality 9)
1624 # compressed_size: 942M | 869M (~8% reduced) | 854M
1625 # compression_time: 75s | 265s | 719s
1626 # decompression_time: 15s | 25s | 25s
1627
1628 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001629 brotli_cmd = ['brotli', '--quality=6',
1630 '--output={}.new.dat.br'.format(self.path),
1631 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001632 print("Compressing {}.new.dat with brotli".format(self.partition))
Alex Deymob10e07a2017-11-09 23:53:42 +01001633 p = Run(brotli_cmd, stdout=subprocess.PIPE)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001634 p.communicate()
1635 assert p.returncode == 0,\
1636 'compression of {}.new.dat failed'.format(self.partition)
1637
1638 new_data_name = '{}.new.dat.br'.format(self.partition)
1639 ZipWrite(output_zip,
1640 '{}.new.dat.br'.format(self.path),
1641 new_data_name,
1642 compress_type=zipfile.ZIP_STORED)
1643 else:
1644 new_data_name = '{}.new.dat'.format(self.partition)
1645 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1646
Dan Albert8e0178d2015-01-27 15:53:15 -08001647 ZipWrite(output_zip,
1648 '{}.patch.dat'.format(self.path),
1649 '{}.patch.dat'.format(self.partition),
1650 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001651
Tianjie Xu209db462016-05-24 17:34:52 -07001652 if self.partition == "system":
1653 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1654 else:
1655 code = ErrorCode.VENDOR_UPDATE_FAILURE
1656
Dan Albert8e0178d2015-01-27 15:53:15 -08001657 call = ('block_image_update("{device}", '
1658 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001659 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001660 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001661 device=self.device, partition=self.partition,
1662 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001663 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001664
Dan Albert8b72aef2015-03-23 19:13:21 -07001665 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001666 data = source.ReadRangeSet(ranges)
1667 ctx = sha1()
1668
1669 for p in data:
1670 ctx.update(p)
1671
1672 return ctx.hexdigest()
1673
Tao Baoe9b61912015-07-09 17:37:49 -07001674 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1675 """Return the hash value for all zero blocks."""
1676 zero_block = '\x00' * 4096
1677 ctx = sha1()
1678 for _ in range(num_blocks):
1679 ctx.update(zero_block)
1680
1681 return ctx.hexdigest()
1682
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001683
1684DataImage = blockimgdiff.DataImage
1685
Doug Zongker96a57e72010-09-26 14:57:41 -07001686# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001687PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001688 "ext4": "EMMC",
1689 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001690 "f2fs": "EMMC",
1691 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001692}
Doug Zongker96a57e72010-09-26 14:57:41 -07001693
1694def GetTypeAndDevice(mount_point, info):
1695 fstab = info["fstab"]
1696 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001697 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1698 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001699 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001700 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001701
1702
1703def ParseCertificate(data):
1704 """Parse a PEM-format certificate."""
1705 cert = []
1706 save = False
1707 for line in data.split("\n"):
1708 if "--END CERTIFICATE--" in line:
1709 break
1710 if save:
1711 cert.append(line)
1712 if "--BEGIN CERTIFICATE--" in line:
1713 save = True
1714 cert = "".join(cert).decode('base64')
1715 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001716
Doug Zongker412c02f2014-02-13 10:58:24 -08001717def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1718 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001719 """Generate a binary patch that creates the recovery image starting
1720 with the boot image. (Most of the space in these images is just the
1721 kernel, which is identical for the two, so the resulting patch
1722 should be efficient.) Add it to the output zip, along with a shell
1723 script that is run from init.rc on first boot to actually do the
1724 patching and install the new recovery image.
1725
1726 recovery_img and boot_img should be File objects for the
1727 corresponding images. info should be the dictionary returned by
1728 common.LoadInfoDict() on the input target_files.
1729 """
1730
Doug Zongker412c02f2014-02-13 10:58:24 -08001731 if info_dict is None:
1732 info_dict = OPTIONS.info_dict
1733
Tao Baof2cffbd2015-07-22 12:33:18 -07001734 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001735
Tao Baof2cffbd2015-07-22 12:33:18 -07001736 if full_recovery_image:
1737 output_sink("etc/recovery.img", recovery_img.data)
1738
1739 else:
1740 diff_program = ["imgdiff"]
1741 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1742 if os.path.exists(path):
1743 diff_program.append("-b")
1744 diff_program.append(path)
1745 bonus_args = "-b /system/etc/recovery-resource.dat"
1746 else:
1747 bonus_args = ""
1748
1749 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1750 _, _, patch = d.ComputePatch()
1751 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001752
Dan Albertebb19aa2015-03-27 19:11:53 -07001753 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001754 # The following GetTypeAndDevice()s need to use the path in the target
1755 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001756 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1757 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1758 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001759 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001760
Tao Baof2cffbd2015-07-22 12:33:18 -07001761 if full_recovery_image:
1762 sh = """#!/system/bin/sh
1763if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1764 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"
1765else
1766 log -t recovery "Recovery image already installed"
1767fi
1768""" % {'type': recovery_type,
1769 'device': recovery_device,
1770 'sha1': recovery_img.sha1,
1771 'size': recovery_img.size}
1772 else:
1773 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001774if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1775 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"
1776else
1777 log -t recovery "Recovery image already installed"
1778fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001779""" % {'boot_size': boot_img.size,
1780 'boot_sha1': boot_img.sha1,
1781 'recovery_size': recovery_img.size,
1782 'recovery_sha1': recovery_img.sha1,
1783 'boot_type': boot_type,
1784 'boot_device': boot_device,
1785 'recovery_type': recovery_type,
1786 'recovery_device': recovery_device,
1787 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001788
1789 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001790 # in the L release.
1791 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001792
Tao Bao89fbb0f2017-01-10 10:47:58 -08001793 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001794
1795 output_sink(sh_location, sh)