blob: 027e9f5ee7ac1fff81f12b21f54a60f24eb22859 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Doug Zongkerea5d7a92010-09-12 15:26:16 -070017import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070018import errno
Doug Zongkereef39442009-04-02 12:14:19 -070019import getopt
20import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070021import imp
Doug Zongkereef39442009-04-02 12:14:19 -070022import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080023import platform
Doug Zongkereef39442009-04-02 12:14:19 -070024import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070025import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070026import shutil
27import subprocess
28import sys
29import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070030import threading
31import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070032import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070033
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034import blockimgdiff
35
Tao Baof3282b42015-04-01 11:21:55 -070036from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080037
Doug Zongkereef39442009-04-02 12:14:19 -070038
Dan Albert8b72aef2015-03-23 19:13:21 -070039class Options(object):
40 def __init__(self):
41 platform_search_path = {
42 "linux2": "out/host/linux-x86",
43 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070044 }
Doug Zongker85448772014-09-09 14:59:20 -070045
Dan Albert8b72aef2015-03-23 19:13:21 -070046 self.search_path = platform_search_path.get(sys.platform, None)
47 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080048 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070049 self.extra_signapk_args = []
50 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080051 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070052 self.public_key_suffix = ".x509.pem"
53 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070054 # use otatools built boot_signer by default
55 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070056 self.boot_signer_args = []
57 self.verity_signer_path = None
58 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070059 self.verbose = False
60 self.tempfiles = []
61 self.device_specific = None
62 self.extras = {}
63 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070064 self.source_info_dict = None
65 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070066 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070067 # Stash size cannot exceed cache_size * threshold.
68 self.cache_size = None
69 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070070
71
72OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070073
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080074
75# Values for "certificate" in apkcerts that mean special things.
76SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
77
Tianjie Xu209db462016-05-24 17:34:52 -070078class ErrorCode(object):
79 """Define error_codes for failures that happen during the actual
80 update package installation.
81
82 Error codes 0-999 are reserved for failures before the package
83 installation (i.e. low battery, package verification failure).
84 Detailed code in 'bootable/recovery/error_code.h' """
85
86 SYSTEM_VERIFICATION_FAILURE = 1000
87 SYSTEM_UPDATE_FAILURE = 1001
88 SYSTEM_UNEXPECTED_CONTENTS = 1002
89 SYSTEM_NONZERO_CONTENTS = 1003
90 SYSTEM_RECOVER_FAILURE = 1004
91 VENDOR_VERIFICATION_FAILURE = 2000
92 VENDOR_UPDATE_FAILURE = 2001
93 VENDOR_UNEXPECTED_CONTENTS = 2002
94 VENDOR_NONZERO_CONTENTS = 2003
95 VENDOR_RECOVER_FAILURE = 2004
96 OEM_PROP_MISMATCH = 3000
97 FINGERPRINT_MISMATCH = 3001
98 THUMBPRINT_MISMATCH = 3002
99 OLDER_BUILD = 3003
100 DEVICE_MISMATCH = 3004
101 BAD_PATCH_FILE = 3005
102 INSUFFICIENT_CACHE_SPACE = 3006
103 TUNE_PARTITION_FAILURE = 3007
104 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800105
Dan Albert8b72aef2015-03-23 19:13:21 -0700106class ExternalError(RuntimeError):
107 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700108
109
110def Run(args, **kwargs):
111 """Create and return a subprocess.Popen object, printing the command
112 line on the terminal if -v was specified."""
113 if OPTIONS.verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800114 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700115 return subprocess.Popen(args, **kwargs)
116
117
Ying Wang7e6d4e42010-12-13 16:25:36 -0800118def CloseInheritedPipes():
119 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
120 before doing other work."""
121 if platform.system() != "Darwin":
122 return
123 for d in range(3, 1025):
124 try:
125 stat = os.fstat(d)
126 if stat is not None:
127 pipebit = stat[0] & 0x1000
128 if pipebit != 0:
129 os.close(d)
130 except OSError:
131 pass
132
133
Tao Bao2c15d9e2015-07-09 11:51:16 -0700134def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700135 """Read and parse the META/misc_info.txt key/value pairs from the
136 input target files and return a dict."""
137
Doug Zongkerc9253822014-02-04 12:17:58 -0800138 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700139 if isinstance(input_file, zipfile.ZipFile):
140 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800141 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700142 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800143 try:
144 with open(path) as f:
145 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700146 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800147 if e.errno == errno.ENOENT:
148 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700149 d = {}
150 try:
Michael Runge6e836112014-04-15 17:40:21 -0700151 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700152 except KeyError:
153 # ok if misc_info.txt doesn't exist
154 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700155
Doug Zongker37974732010-09-16 17:44:38 -0700156 # backwards compatibility: These values used to be in their own
157 # files. Look for them, in case we're processing an old
158 # target_files zip.
159
Doug Zongker37974732010-09-16 17:44:38 -0700160 if "recovery_api_version" not in d:
161 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700162 d["recovery_api_version"] = read_helper(
163 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700164 except KeyError:
165 raise ValueError("can't find recovery API version in input target-files")
166
167 if "tool_extensions" not in d:
168 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800169 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700170 except KeyError:
171 # ok if extensions don't exist
172 pass
173
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800174 if "fstab_version" not in d:
175 d["fstab_version"] = "1"
176
Tao Bao84e75682015-07-19 02:38:53 -0700177 # A few properties are stored as links to the files in the out/ directory.
178 # It works fine with the build system. However, they are no longer available
179 # when (re)generating from target_files zip. If input_dir is not None, we
180 # are doing repacking. Redirect those properties to the actual files in the
181 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700182 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400183 # We carry a copy of file_contexts.bin under META/. If not available,
184 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700185 # to build images than the one running on device, such as when enabling
186 # system_root_image. In that case, we must have the one for image
187 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700188 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
189 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700190 if d.get("system_root_image") == "true":
191 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700192 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700193 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700194 if not os.path.exists(fc_config):
195 fc_config = None
196
197 if fc_config:
198 d["selinux_fc"] = fc_config
199
Tao Bao84e75682015-07-19 02:38:53 -0700200 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
201 if d.get("system_root_image") == "true":
202 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
203 d["ramdisk_fs_config"] = os.path.join(
204 input_dir, "META", "root_filesystem_config.txt")
205
Tao Baof54216f2016-03-29 15:12:37 -0700206 # Redirect {system,vendor}_base_fs_file.
207 if "system_base_fs_file" in d:
208 basename = os.path.basename(d["system_base_fs_file"])
209 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700210 if os.path.exists(system_base_fs_file):
211 d["system_base_fs_file"] = system_base_fs_file
212 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800213 print("Warning: failed to find system base fs file: %s" % (
214 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700215 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700216
217 if "vendor_base_fs_file" in d:
218 basename = os.path.basename(d["vendor_base_fs_file"])
219 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700220 if os.path.exists(vendor_base_fs_file):
221 d["vendor_base_fs_file"] = vendor_base_fs_file
222 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800223 print("Warning: failed to find vendor base fs file: %s" % (
224 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700225 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700226
Doug Zongker37974732010-09-16 17:44:38 -0700227 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800228 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700229 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700230 if not line:
231 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700232 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700233 if not value:
234 continue
Doug Zongker37974732010-09-16 17:44:38 -0700235 if name == "blocksize":
236 d[name] = value
237 else:
238 d[name + "_size"] = value
239 except KeyError:
240 pass
241
242 def makeint(key):
243 if key in d:
244 d[key] = int(d[key], 0)
245
246 makeint("recovery_api_version")
247 makeint("blocksize")
248 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700249 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700250 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700251 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700252 makeint("recovery_size")
253 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800254 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700255
Tianjie Xucfa86222016-03-07 16:31:19 -0800256 system_root_image = d.get("system_root_image", None) == "true"
257 if d.get("no_recovery", None) != "true":
258 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800259 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800260 recovery_fstab_path, system_root_image)
261 elif d.get("recovery_as_boot", None) == "true":
262 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
263 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
264 recovery_fstab_path, system_root_image)
265 else:
266 d["fstab"] = None
267
Doug Zongkerc9253822014-02-04 12:17:58 -0800268 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700269 return d
270
Doug Zongkerc9253822014-02-04 12:17:58 -0800271def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700272 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800273 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700274 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800275 print("Warning: could not find SYSTEM/build.prop in %s" % (zip,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700276 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700277 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700278
Michael Runge6e836112014-04-15 17:40:21 -0700279def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700280 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700281 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700282 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700283 if not line or line.startswith("#"):
284 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700285 if "=" in line:
286 name, value = line.split("=", 1)
287 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700288 return d
289
Tianjie Xucfa86222016-03-07 16:31:19 -0800290def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
291 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700292 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700293 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700294 self.mount_point = mount_point
295 self.fs_type = fs_type
296 self.device = device
297 self.length = length
298 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700299 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700300
301 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800302 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700303 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800304 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700305 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700306
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800307 if fstab_version == 1:
308 d = {}
309 for line in data.split("\n"):
310 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700311 if not line or line.startswith("#"):
312 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800313 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700314 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800315 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800316 options = None
317 if len(pieces) >= 4:
318 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700319 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800320 if len(pieces) >= 5:
321 options = pieces[4]
322 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700323 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800324 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800325 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700326 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700327
Dan Albert8b72aef2015-03-23 19:13:21 -0700328 mount_point = pieces[0]
329 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800330 if options:
331 options = options.split(",")
332 for i in options:
333 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700334 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800335 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800336 print("%s: unknown option \"%s\"" % (mount_point, i))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800337
Dan Albert8b72aef2015-03-23 19:13:21 -0700338 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
339 device=pieces[2], length=length,
340 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800341
342 elif fstab_version == 2:
343 d = {}
344 for line in data.split("\n"):
345 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700346 if not line or line.startswith("#"):
347 continue
Tao Bao548eb762015-06-10 12:32:41 -0700348 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800349 pieces = line.split()
350 if len(pieces) != 5:
351 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
352
353 # Ignore entries that are managed by vold
354 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700355 if "voldmanaged=" in options:
356 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800357
358 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700359 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800360 options = options.split(",")
361 for i in options:
362 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700363 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800364 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800365 # Ignore all unknown options in the unified fstab
366 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800367
Tao Bao548eb762015-06-10 12:32:41 -0700368 mount_flags = pieces[3]
369 # Honor the SELinux context if present.
370 context = None
371 for i in mount_flags.split(","):
372 if i.startswith("context="):
373 context = i
374
Dan Albert8b72aef2015-03-23 19:13:21 -0700375 mount_point = pieces[1]
376 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700377 device=pieces[0], length=length,
378 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800379
380 else:
381 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
382
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700383 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700384 # system. Other areas assume system is always at "/system" so point /system
385 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700386 if system_root_image:
387 assert not d.has_key("/system") and d.has_key("/")
388 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700389 return d
390
391
Doug Zongker37974732010-09-16 17:44:38 -0700392def DumpInfoDict(d):
393 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800394 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700395
Dan Albert8b72aef2015-03-23 19:13:21 -0700396
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400397def AppendAVBSigningArgs(cmd):
398 """Append signing arguments for avbtool."""
399 keypath = OPTIONS.info_dict.get("board_avb_key_path", None)
400 algorithm = OPTIONS.info_dict.get("board_avb_algorithm", None)
401 if not keypath or not algorithm:
402 algorithm = "SHA256_RSA4096"
403 keypath = "external/avb/test/data/testkey_rsa4096.pem"
404 cmd.extend(["--key", keypath, "--algorithm", algorithm])
405
406
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700407def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800408 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700409 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700410
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700411 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800412 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
413 we are building a two-step special image (i.e. building a recovery image to
414 be loaded into /boot in two-step OTAs).
415
416 Return the image data, or None if sourcedir does not appear to contains files
417 for building the requested image.
418 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700419
420 def make_ramdisk():
421 ramdisk_img = tempfile.NamedTemporaryFile()
422
423 if os.access(fs_config_file, os.F_OK):
424 cmd = ["mkbootfs", "-f", fs_config_file,
425 os.path.join(sourcedir, "RAMDISK")]
426 else:
427 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
428 p1 = Run(cmd, stdout=subprocess.PIPE)
429 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
430
431 p2.wait()
432 p1.wait()
433 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
434 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
435
436 return ramdisk_img
437
438 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
439 return None
440
441 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700442 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700443
Doug Zongkerd5131602012-08-02 14:46:42 -0700444 if info_dict is None:
445 info_dict = OPTIONS.info_dict
446
Doug Zongkereef39442009-04-02 12:14:19 -0700447 img = tempfile.NamedTemporaryFile()
448
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700449 if has_ramdisk:
450 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700451
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800452 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
453 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
454
455 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700456
Benoit Fradina45a8682014-07-14 21:00:43 +0200457 fn = os.path.join(sourcedir, "second")
458 if os.access(fn, os.F_OK):
459 cmd.append("--second")
460 cmd.append(fn)
461
Doug Zongker171f1cd2009-06-15 22:36:37 -0700462 fn = os.path.join(sourcedir, "cmdline")
463 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700464 cmd.append("--cmdline")
465 cmd.append(open(fn).read().rstrip("\n"))
466
467 fn = os.path.join(sourcedir, "base")
468 if os.access(fn, os.F_OK):
469 cmd.append("--base")
470 cmd.append(open(fn).read().rstrip("\n"))
471
Ying Wang4de6b5b2010-08-25 14:29:34 -0700472 fn = os.path.join(sourcedir, "pagesize")
473 if os.access(fn, os.F_OK):
474 cmd.append("--pagesize")
475 cmd.append(open(fn).read().rstrip("\n"))
476
Doug Zongkerd5131602012-08-02 14:46:42 -0700477 args = info_dict.get("mkbootimg_args", None)
478 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700479 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700480
Sami Tolvanen3303d902016-03-15 16:49:30 +0000481 args = info_dict.get("mkbootimg_version_args", None)
482 if args and args.strip():
483 cmd.extend(shlex.split(args))
484
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700485 if has_ramdisk:
486 cmd.extend(["--ramdisk", ramdisk_img.name])
487
Tao Baod95e9fd2015-03-29 23:07:41 -0700488 img_unsigned = None
489 if info_dict.get("vboot", None):
490 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700491 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700492 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700493 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700494
495 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700496 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700497 assert p.returncode == 0, "mkbootimg of %s image failed" % (
498 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700499
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100500 if (info_dict.get("boot_signer", None) == "true" and
501 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800502 # Hard-code the path as "/boot" for two-step special recovery image (which
503 # will be loaded into /boot during the two-step OTA).
504 if two_step_image:
505 path = "/boot"
506 else:
507 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700508 cmd = [OPTIONS.boot_signer_path]
509 cmd.extend(OPTIONS.boot_signer_args)
510 cmd.extend([path, img.name,
511 info_dict["verity_key"] + ".pk8",
512 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700513 p = Run(cmd, stdout=subprocess.PIPE)
514 p.communicate()
515 assert p.returncode == 0, "boot_signer of %s image failed" % path
516
Tao Baod95e9fd2015-03-29 23:07:41 -0700517 # Sign the image if vboot is non-empty.
518 elif info_dict.get("vboot", None):
519 path = "/" + os.path.basename(sourcedir).lower()
520 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800521 # We have switched from the prebuilt futility binary to using the tool
522 # (futility-host) built from the source. Override the setting in the old
523 # TF.zip.
524 futility = info_dict["futility"]
525 if futility.startswith("prebuilts/"):
526 futility = "futility-host"
527 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700528 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700529 info_dict["vboot_key"] + ".vbprivk",
530 info_dict["vboot_subkey"] + ".vbprivk",
531 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700532 img.name]
533 p = Run(cmd, stdout=subprocess.PIPE)
534 p.communicate()
535 assert p.returncode == 0, "vboot_signer of %s image failed" % path
536
Tao Baof3282b42015-04-01 11:21:55 -0700537 # Clean up the temp files.
538 img_unsigned.close()
539 img_keyblock.close()
540
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400541 # AVB: if enabled, calculate and add hash to boot.img.
Tao Baob31b94e2016-09-29 21:59:06 -0700542 if info_dict.get("board_avb_enable", None) == "true":
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400543 avbtool = os.getenv('AVBTOOL') or "avbtool"
Tao Baob31b94e2016-09-29 21:59:06 -0700544 part_size = info_dict.get("boot_size", None)
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400545 cmd = [avbtool, "add_hash_footer", "--image", img.name,
546 "--partition_size", str(part_size), "--partition_name", "boot"]
547 AppendAVBSigningArgs(cmd)
Tao Baob31b94e2016-09-29 21:59:06 -0700548 args = info_dict.get("board_avb_boot_add_hash_footer_args", None)
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400549 if args and args.strip():
550 cmd.extend(shlex.split(args))
551 p = Run(cmd, stdout=subprocess.PIPE)
552 p.communicate()
553 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
554 os.path.basename(OPTIONS.input_tmp))
David Zeuthend995f4b2016-01-29 16:59:17 -0500555
556 img.seek(os.SEEK_SET, 0)
557 data = img.read()
558
559 if has_ramdisk:
560 ramdisk_img.close()
561 img.close()
562
563 return data
564
565
Doug Zongkerd5131602012-08-02 14:46:42 -0700566def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800567 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700568 """Return a File object with the desired bootable image.
569
570 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
571 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
572 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700573
Doug Zongker55d93282011-01-25 17:03:34 -0800574 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
575 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800576 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800577 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700578
579 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
580 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800581 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700582 return File.FromLocalFile(name, prebuilt_path)
583
Tao Bao89fbb0f2017-01-10 10:47:58 -0800584 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700585
586 if info_dict is None:
587 info_dict = OPTIONS.info_dict
588
589 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800590 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
591 # for recovery.
592 has_ramdisk = (info_dict.get("system_root_image") != "true" or
593 prebuilt_name != "boot.img" or
594 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700595
Doug Zongker6f1d0312014-08-22 08:07:12 -0700596 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400597 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
598 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800599 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700600 if data:
601 return File(name, data)
602 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800603
Doug Zongkereef39442009-04-02 12:14:19 -0700604
Doug Zongker75f17362009-12-08 13:46:44 -0800605def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800606 """Unzip the given archive into a temporary directory and return the name.
607
608 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
609 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
610
611 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
612 main file), open for reading.
613 """
Doug Zongkereef39442009-04-02 12:14:19 -0700614
615 tmp = tempfile.mkdtemp(prefix="targetfiles-")
616 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800617
618 def unzip_to_dir(filename, dirname):
619 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
620 if pattern is not None:
621 cmd.append(pattern)
622 p = Run(cmd, stdout=subprocess.PIPE)
623 p.communicate()
624 if p.returncode != 0:
625 raise ExternalError("failed to unzip input target-files \"%s\"" %
626 (filename,))
627
628 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
629 if m:
630 unzip_to_dir(m.group(1), tmp)
631 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
632 filename = m.group(1)
633 else:
634 unzip_to_dir(filename, tmp)
635
636 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700637
638
639def GetKeyPasswords(keylist):
640 """Given a list of keys, prompt the user to enter passwords for
641 those which require them. Return a {key: password} dict. password
642 will be None if the key has no password."""
643
Doug Zongker8ce7c252009-05-22 13:34:54 -0700644 no_passwords = []
645 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700646 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700647 devnull = open("/dev/null", "w+b")
648 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800649 # We don't need a password for things that aren't really keys.
650 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700651 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700652 continue
653
T.R. Fullhart37e10522013-03-18 10:31:26 -0700654 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700655 "-inform", "DER", "-nocrypt"],
656 stdin=devnull.fileno(),
657 stdout=devnull.fileno(),
658 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700659 p.communicate()
660 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700661 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700662 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700663 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700664 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
665 "-inform", "DER", "-passin", "pass:"],
666 stdin=devnull.fileno(),
667 stdout=devnull.fileno(),
668 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700669 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700670 if p.returncode == 0:
671 # Encrypted key with empty string as password.
672 key_passwords[k] = ''
673 elif stderr.startswith('Error decrypting key'):
674 # Definitely encrypted key.
675 # It would have said "Error reading key" if it didn't parse correctly.
676 need_passwords.append(k)
677 else:
678 # Potentially, a type of key that openssl doesn't understand.
679 # We'll let the routines in signapk.jar handle it.
680 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700681 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700682
T.R. Fullhart37e10522013-03-18 10:31:26 -0700683 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700684 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700685 return key_passwords
686
687
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800688def GetMinSdkVersion(apk_name):
689 """Get the minSdkVersion delared in the APK. This can be both a decimal number
690 (API Level) or a codename.
691 """
692
693 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
694 output, err = p.communicate()
695 if err:
696 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
697 % (p.returncode,))
698
699 for line in output.split("\n"):
700 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
701 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
702 if m:
703 return m.group(1)
704 raise ExternalError("No minSdkVersion returned by aapt")
705
706
707def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
708 """Get the minSdkVersion declared in the APK as a number (API Level). If
709 minSdkVersion is set to a codename, it is translated to a number using the
710 provided map.
711 """
712
713 version = GetMinSdkVersion(apk_name)
714 try:
715 return int(version)
716 except ValueError:
717 # Not a decimal number. Codename?
718 if version in codename_to_api_level_map:
719 return codename_to_api_level_map[version]
720 else:
721 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
722 % (version, codename_to_api_level_map))
723
724
725def SignFile(input_name, output_name, key, password, min_api_level=None,
726 codename_to_api_level_map=dict(),
727 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700728 """Sign the input_name zip/jar/apk, producing output_name. Use the
729 given key and password (the latter may be None if the key does not
730 have a password.
731
Doug Zongker951495f2009-08-14 12:44:19 -0700732 If whole_file is true, use the "-w" option to SignApk to embed a
733 signature that covers the whole file in the archive comment of the
734 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800735
736 min_api_level is the API Level (int) of the oldest platform this file may end
737 up on. If not specified for an APK, the API Level is obtained by interpreting
738 the minSdkVersion attribute of the APK's AndroidManifest.xml.
739
740 codename_to_api_level_map is needed to translate the codename which may be
741 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700742 """
Doug Zongker951495f2009-08-14 12:44:19 -0700743
Alex Klyubin9667b182015-12-10 13:38:50 -0800744 java_library_path = os.path.join(
745 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
746
Tao Baoe95540e2016-11-08 12:08:53 -0800747 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
748 ["-Djava.library.path=" + java_library_path,
749 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
750 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700751 if whole_file:
752 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800753
754 min_sdk_version = min_api_level
755 if min_sdk_version is None:
756 if not whole_file:
757 min_sdk_version = GetMinSdkVersionInt(
758 input_name, codename_to_api_level_map)
759 if min_sdk_version is not None:
760 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
761
T.R. Fullhart37e10522013-03-18 10:31:26 -0700762 cmd.extend([key + OPTIONS.public_key_suffix,
763 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800764 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700765
766 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700767 if password is not None:
768 password += "\n"
769 p.communicate(password)
770 if p.returncode != 0:
771 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
772
Doug Zongkereef39442009-04-02 12:14:19 -0700773
Doug Zongker37974732010-09-16 17:44:38 -0700774def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700775 """Check the data string passed against the max size limit, if
776 any, for the given target. Raise exception if the data is too big.
777 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700778
Dan Albert8b72aef2015-03-23 19:13:21 -0700779 if target.endswith(".img"):
780 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700781 mount_point = "/" + target
782
Ying Wangf8824af2014-06-03 14:07:27 -0700783 fs_type = None
784 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700785 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700786 if mount_point == "/userdata":
787 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700788 p = info_dict["fstab"][mount_point]
789 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800790 device = p.device
791 if "/" in device:
792 device = device[device.rfind("/")+1:]
793 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700794 if not fs_type or not limit:
795 return
Doug Zongkereef39442009-04-02 12:14:19 -0700796
Andrew Boie0f9aec82012-02-14 09:32:52 -0800797 size = len(data)
798 pct = float(size) * 100.0 / limit
799 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
800 if pct >= 99.0:
801 raise ExternalError(msg)
802 elif pct >= 95.0:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800803 print("\n WARNING: %s\n" % (msg,))
Andrew Boie0f9aec82012-02-14 09:32:52 -0800804 elif OPTIONS.verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800805 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700806
807
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800808def ReadApkCerts(tf_zip):
809 """Given a target_files ZipFile, parse the META/apkcerts.txt file
810 and return a {package: cert} dict."""
811 certmap = {}
812 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
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800816 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
817 r'private_key="(.*)"$', line)
818 if m:
819 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700820 public_key_suffix_len = len(OPTIONS.public_key_suffix)
821 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800822 if cert in SPECIAL_CERT_STRINGS and not privkey:
823 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700824 elif (cert.endswith(OPTIONS.public_key_suffix) and
825 privkey.endswith(OPTIONS.private_key_suffix) and
826 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
827 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800828 else:
829 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
830 return certmap
831
832
Doug Zongkereef39442009-04-02 12:14:19 -0700833COMMON_DOCSTRING = """
834 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700835 Prepend <dir>/bin to the list of places to search for binaries
836 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700837
Doug Zongker05d3dea2009-06-22 11:32:31 -0700838 -s (--device_specific) <file>
839 Path to the python module containing device-specific
840 releasetools code.
841
Doug Zongker8bec09e2009-11-30 15:37:14 -0800842 -x (--extra) <key=value>
843 Add a key/value pair to the 'extras' dict, which device-specific
844 extension code may look at.
845
Doug Zongkereef39442009-04-02 12:14:19 -0700846 -v (--verbose)
847 Show command lines being executed.
848
849 -h (--help)
850 Display this usage message and exit.
851"""
852
853def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800854 print(docstring.rstrip("\n"))
855 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700856
857
858def ParseOptions(argv,
859 docstring,
860 extra_opts="", extra_long_opts=(),
861 extra_option_handler=None):
862 """Parse the options in argv and return any arguments that aren't
863 flags. docstring is the calling module's docstring, to be displayed
864 for errors and -h. extra_opts and extra_long_opts are for flags
865 defined by the caller, which are processed by passing them to
866 extra_option_handler."""
867
868 try:
869 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800870 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800871 ["help", "verbose", "path=", "signapk_path=",
872 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700873 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700874 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
875 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800876 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700877 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700878 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700879 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800880 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700881 sys.exit(2)
882
Doug Zongkereef39442009-04-02 12:14:19 -0700883 for o, a in opts:
884 if o in ("-h", "--help"):
885 Usage(docstring)
886 sys.exit()
887 elif o in ("-v", "--verbose"):
888 OPTIONS.verbose = True
889 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700890 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700891 elif o in ("--signapk_path",):
892 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800893 elif o in ("--signapk_shared_library_path",):
894 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700895 elif o in ("--extra_signapk_args",):
896 OPTIONS.extra_signapk_args = shlex.split(a)
897 elif o in ("--java_path",):
898 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700899 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800900 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700901 elif o in ("--public_key_suffix",):
902 OPTIONS.public_key_suffix = a
903 elif o in ("--private_key_suffix",):
904 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800905 elif o in ("--boot_signer_path",):
906 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700907 elif o in ("--boot_signer_args",):
908 OPTIONS.boot_signer_args = shlex.split(a)
909 elif o in ("--verity_signer_path",):
910 OPTIONS.verity_signer_path = a
911 elif o in ("--verity_signer_args",):
912 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700913 elif o in ("-s", "--device_specific"):
914 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800915 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800916 key, value = a.split("=", 1)
917 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700918 else:
919 if extra_option_handler is None or not extra_option_handler(o, a):
920 assert False, "unknown option \"%s\"" % (o,)
921
Doug Zongker85448772014-09-09 14:59:20 -0700922 if OPTIONS.search_path:
923 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
924 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700925
926 return args
927
928
Doug Zongkerfc44a512014-08-26 13:10:25 -0700929def MakeTempFile(prefix=None, suffix=None):
930 """Make a temp file and add it to the list of things to be deleted
931 when Cleanup() is called. Return the filename."""
932 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
933 os.close(fd)
934 OPTIONS.tempfiles.append(fn)
935 return fn
936
937
Doug Zongkereef39442009-04-02 12:14:19 -0700938def Cleanup():
939 for i in OPTIONS.tempfiles:
940 if os.path.isdir(i):
941 shutil.rmtree(i)
942 else:
943 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700944
945
946class PasswordManager(object):
947 def __init__(self):
948 self.editor = os.getenv("EDITOR", None)
949 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
950
951 def GetPasswords(self, items):
952 """Get passwords corresponding to each string in 'items',
953 returning a dict. (The dict may have keys in addition to the
954 values in 'items'.)
955
956 Uses the passwords in $ANDROID_PW_FILE if available, letting the
957 user edit that file to add more needed passwords. If no editor is
958 available, or $ANDROID_PW_FILE isn't define, prompts the user
959 interactively in the ordinary way.
960 """
961
962 current = self.ReadFile()
963
964 first = True
965 while True:
966 missing = []
967 for i in items:
968 if i not in current or not current[i]:
969 missing.append(i)
970 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700971 if not missing:
972 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700973
974 for i in missing:
975 current[i] = ""
976
977 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800978 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700979 answer = raw_input("try to edit again? [y]> ").strip()
980 if answer and answer[0] not in 'yY':
981 raise RuntimeError("key passwords unavailable")
982 first = False
983
984 current = self.UpdateAndReadFile(current)
985
Dan Albert8b72aef2015-03-23 19:13:21 -0700986 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700987 """Prompt the user to enter a value (password) for each key in
988 'current' whose value is fales. Returns a new dict with all the
989 values.
990 """
991 result = {}
992 for k, v in sorted(current.iteritems()):
993 if v:
994 result[k] = v
995 else:
996 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700997 result[k] = getpass.getpass(
998 "Enter password for %s key> " % k).strip()
999 if result[k]:
1000 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001001 return result
1002
1003 def UpdateAndReadFile(self, current):
1004 if not self.editor or not self.pwfile:
1005 return self.PromptResult(current)
1006
1007 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001008 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001009 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1010 f.write("# (Additional spaces are harmless.)\n\n")
1011
1012 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001013 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1014 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001015 f.write("[[[ %s ]]] %s\n" % (v, k))
1016 if not v and first_line is None:
1017 # position cursor on first line with no password.
1018 first_line = i + 4
1019 f.close()
1020
1021 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1022 _, _ = p.communicate()
1023
1024 return self.ReadFile()
1025
1026 def ReadFile(self):
1027 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001028 if self.pwfile is None:
1029 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001030 try:
1031 f = open(self.pwfile, "r")
1032 for line in f:
1033 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001034 if not line or line[0] == '#':
1035 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001036 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1037 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001038 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001039 else:
1040 result[m.group(2)] = m.group(1)
1041 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001042 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001043 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001044 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001045 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001046
1047
Dan Albert8e0178d2015-01-27 15:53:15 -08001048def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1049 compress_type=None):
1050 import datetime
1051
1052 # http://b/18015246
1053 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1054 # for files larger than 2GiB. We can work around this by adjusting their
1055 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1056 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1057 # it isn't clear to me exactly what circumstances cause this).
1058 # `zipfile.write()` must be used directly to work around this.
1059 #
1060 # This mess can be avoided if we port to python3.
1061 saved_zip64_limit = zipfile.ZIP64_LIMIT
1062 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1063
1064 if compress_type is None:
1065 compress_type = zip_file.compression
1066 if arcname is None:
1067 arcname = filename
1068
1069 saved_stat = os.stat(filename)
1070
1071 try:
1072 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1073 # file to be zipped and reset it when we're done.
1074 os.chmod(filename, perms)
1075
1076 # Use a fixed timestamp so the output is repeatable.
1077 epoch = datetime.datetime.fromtimestamp(0)
1078 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1079 os.utime(filename, (timestamp, timestamp))
1080
1081 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1082 finally:
1083 os.chmod(filename, saved_stat.st_mode)
1084 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1085 zipfile.ZIP64_LIMIT = saved_zip64_limit
1086
1087
Tao Bao58c1b962015-05-20 09:32:18 -07001088def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001089 compress_type=None):
1090 """Wrap zipfile.writestr() function to work around the zip64 limit.
1091
1092 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1093 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1094 when calling crc32(bytes).
1095
1096 But it still works fine to write a shorter string into a large zip file.
1097 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1098 when we know the string won't be too long.
1099 """
1100
1101 saved_zip64_limit = zipfile.ZIP64_LIMIT
1102 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1103
1104 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1105 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001106 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001107 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001108 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001109 else:
Tao Baof3282b42015-04-01 11:21:55 -07001110 zinfo = zinfo_or_arcname
1111
1112 # If compress_type is given, it overrides the value in zinfo.
1113 if compress_type is not None:
1114 zinfo.compress_type = compress_type
1115
Tao Bao58c1b962015-05-20 09:32:18 -07001116 # If perms is given, it has a priority.
1117 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001118 # If perms doesn't set the file type, mark it as a regular file.
1119 if perms & 0o770000 == 0:
1120 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001121 zinfo.external_attr = perms << 16
1122
Tao Baof3282b42015-04-01 11:21:55 -07001123 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001124 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1125
Dan Albert8b72aef2015-03-23 19:13:21 -07001126 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001127 zipfile.ZIP64_LIMIT = saved_zip64_limit
1128
1129
1130def ZipClose(zip_file):
1131 # http://b/18015246
1132 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1133 # central directory.
1134 saved_zip64_limit = zipfile.ZIP64_LIMIT
1135 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1136
1137 zip_file.close()
1138
1139 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001140
1141
1142class DeviceSpecificParams(object):
1143 module = None
1144 def __init__(self, **kwargs):
1145 """Keyword arguments to the constructor become attributes of this
1146 object, which is passed to all functions in the device-specific
1147 module."""
1148 for k, v in kwargs.iteritems():
1149 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001150 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001151
1152 if self.module is None:
1153 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001154 if not path:
1155 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001156 try:
1157 if os.path.isdir(path):
1158 info = imp.find_module("releasetools", [path])
1159 else:
1160 d, f = os.path.split(path)
1161 b, x = os.path.splitext(f)
1162 if x == ".py":
1163 f = b
1164 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001165 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001166 self.module = imp.load_module("device_specific", *info)
1167 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001168 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001169
1170 def _DoCall(self, function_name, *args, **kwargs):
1171 """Call the named function in the device-specific module, passing
1172 the given args and kwargs. The first argument to the call will be
1173 the DeviceSpecific object itself. If there is no module, or the
1174 module does not define the function, return the value of the
1175 'default' kwarg (which itself defaults to None)."""
1176 if self.module is None or not hasattr(self.module, function_name):
1177 return kwargs.get("default", None)
1178 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1179
1180 def FullOTA_Assertions(self):
1181 """Called after emitting the block of assertions at the top of a
1182 full OTA package. Implementations can add whatever additional
1183 assertions they like."""
1184 return self._DoCall("FullOTA_Assertions")
1185
Doug Zongkere5ff5902012-01-17 10:55:37 -08001186 def FullOTA_InstallBegin(self):
1187 """Called at the start of full OTA installation."""
1188 return self._DoCall("FullOTA_InstallBegin")
1189
Doug Zongker05d3dea2009-06-22 11:32:31 -07001190 def FullOTA_InstallEnd(self):
1191 """Called at the end of full OTA installation; typically this is
1192 used to install the image for the device's baseband processor."""
1193 return self._DoCall("FullOTA_InstallEnd")
1194
1195 def IncrementalOTA_Assertions(self):
1196 """Called after emitting the block of assertions at the top of an
1197 incremental OTA package. Implementations can add whatever
1198 additional assertions they like."""
1199 return self._DoCall("IncrementalOTA_Assertions")
1200
Doug Zongkere5ff5902012-01-17 10:55:37 -08001201 def IncrementalOTA_VerifyBegin(self):
1202 """Called at the start of the verification phase of incremental
1203 OTA installation; additional checks can be placed here to abort
1204 the script before any changes are made."""
1205 return self._DoCall("IncrementalOTA_VerifyBegin")
1206
Doug Zongker05d3dea2009-06-22 11:32:31 -07001207 def IncrementalOTA_VerifyEnd(self):
1208 """Called at the end of the verification phase of incremental OTA
1209 installation; additional checks can be placed here to abort the
1210 script before any changes are made."""
1211 return self._DoCall("IncrementalOTA_VerifyEnd")
1212
Doug Zongkere5ff5902012-01-17 10:55:37 -08001213 def IncrementalOTA_InstallBegin(self):
1214 """Called at the start of incremental OTA installation (after
1215 verification is complete)."""
1216 return self._DoCall("IncrementalOTA_InstallBegin")
1217
Doug Zongker05d3dea2009-06-22 11:32:31 -07001218 def IncrementalOTA_InstallEnd(self):
1219 """Called at the end of incremental OTA installation; typically
1220 this is used to install the image for the device's baseband
1221 processor."""
1222 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001223
Tao Bao9bc6bb22015-11-09 16:58:28 -08001224 def VerifyOTA_Assertions(self):
1225 return self._DoCall("VerifyOTA_Assertions")
1226
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001227class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001228 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001229 self.name = name
1230 self.data = data
1231 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001232 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001233 self.sha1 = sha1(data).hexdigest()
1234
1235 @classmethod
1236 def FromLocalFile(cls, name, diskname):
1237 f = open(diskname, "rb")
1238 data = f.read()
1239 f.close()
1240 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001241
1242 def WriteToTemp(self):
1243 t = tempfile.NamedTemporaryFile()
1244 t.write(self.data)
1245 t.flush()
1246 return t
1247
Geremy Condra36bd3652014-02-06 19:45:10 -08001248 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001249 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001250
1251DIFF_PROGRAM_BY_EXT = {
1252 ".gz" : "imgdiff",
1253 ".zip" : ["imgdiff", "-z"],
1254 ".jar" : ["imgdiff", "-z"],
1255 ".apk" : ["imgdiff", "-z"],
1256 ".img" : "imgdiff",
1257 }
1258
1259class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001260 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001261 self.tf = tf
1262 self.sf = sf
1263 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001264 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001265
1266 def ComputePatch(self):
1267 """Compute the patch (as a string of data) needed to turn sf into
1268 tf. Returns the same tuple as GetPatch()."""
1269
1270 tf = self.tf
1271 sf = self.sf
1272
Doug Zongker24cd2802012-08-14 16:36:15 -07001273 if self.diff_program:
1274 diff_program = self.diff_program
1275 else:
1276 ext = os.path.splitext(tf.name)[1]
1277 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001278
1279 ttemp = tf.WriteToTemp()
1280 stemp = sf.WriteToTemp()
1281
1282 ext = os.path.splitext(tf.name)[1]
1283
1284 try:
1285 ptemp = tempfile.NamedTemporaryFile()
1286 if isinstance(diff_program, list):
1287 cmd = copy.copy(diff_program)
1288 else:
1289 cmd = [diff_program]
1290 cmd.append(stemp.name)
1291 cmd.append(ttemp.name)
1292 cmd.append(ptemp.name)
1293 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001294 err = []
1295 def run():
1296 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001297 if e:
1298 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001299 th = threading.Thread(target=run)
1300 th.start()
1301 th.join(timeout=300) # 5 mins
1302 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001303 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001304 p.terminate()
1305 th.join(5)
1306 if th.is_alive():
1307 p.kill()
1308 th.join()
1309
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001310 if err or p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001311 print("WARNING: failure running %s:\n%s\n" % (
1312 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001313 self.patch = None
1314 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001315 diff = ptemp.read()
1316 finally:
1317 ptemp.close()
1318 stemp.close()
1319 ttemp.close()
1320
1321 self.patch = diff
1322 return self.tf, self.sf, self.patch
1323
1324
1325 def GetPatch(self):
1326 """Return a tuple (target_file, source_file, patch_data).
1327 patch_data may be None if ComputePatch hasn't been called, or if
1328 computing the patch failed."""
1329 return self.tf, self.sf, self.patch
1330
1331
1332def ComputeDifferences(diffs):
1333 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001334 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001335
1336 # Do the largest files first, to try and reduce the long-pole effect.
1337 by_size = [(i.tf.size, i) for i in diffs]
1338 by_size.sort(reverse=True)
1339 by_size = [i[1] for i in by_size]
1340
1341 lock = threading.Lock()
1342 diff_iter = iter(by_size) # accessed under lock
1343
1344 def worker():
1345 try:
1346 lock.acquire()
1347 for d in diff_iter:
1348 lock.release()
1349 start = time.time()
1350 d.ComputePatch()
1351 dur = time.time() - start
1352 lock.acquire()
1353
1354 tf, sf, patch = d.GetPatch()
1355 if sf.name == tf.name:
1356 name = tf.name
1357 else:
1358 name = "%s (%s)" % (tf.name, sf.name)
1359 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001360 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001361 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001362 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1363 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001364 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001365 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001366 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001367 raise
1368
1369 # start worker threads; wait for them all to finish.
1370 threads = [threading.Thread(target=worker)
1371 for i in range(OPTIONS.worker_threads)]
1372 for th in threads:
1373 th.start()
1374 while threads:
1375 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001376
1377
Dan Albert8b72aef2015-03-23 19:13:21 -07001378class BlockDifference(object):
1379 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001380 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001381 self.tgt = tgt
1382 self.src = src
1383 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001384 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001385 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001386
Tao Baodd2a5892015-03-12 12:32:37 -07001387 if version is None:
1388 version = 1
1389 if OPTIONS.info_dict:
1390 version = max(
1391 int(i) for i in
1392 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1393 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001394
1395 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001396 version=self.version,
1397 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001398 tmpdir = tempfile.mkdtemp()
1399 OPTIONS.tempfiles.append(tmpdir)
1400 self.path = os.path.join(tmpdir, partition)
1401 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001402 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001403 self.touched_src_ranges = b.touched_src_ranges
1404 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001405
Tao Baoaac4ad52015-10-16 15:26:34 -07001406 if src is None:
1407 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1408 else:
1409 _, self.device = GetTypeAndDevice("/" + partition,
1410 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001411
Tao Baod8d14be2016-02-04 14:26:02 -08001412 @property
1413 def required_cache(self):
1414 return self._required_cache
1415
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001416 def WriteScript(self, script, output_zip, progress=None):
1417 if not self.src:
1418 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001419 script.Print("Patching %s image unconditionally..." % (self.partition,))
1420 else:
1421 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001422
Dan Albert8b72aef2015-03-23 19:13:21 -07001423 if progress:
1424 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001425 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001426 if OPTIONS.verify:
1427 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001428
Tao Bao9bc6bb22015-11-09 16:58:28 -08001429 def WriteStrictVerifyScript(self, script):
1430 """Verify all the blocks in the care_map, including clobbered blocks.
1431
1432 This differs from the WriteVerifyScript() function: a) it prints different
1433 error messages; b) it doesn't allow half-way updated images to pass the
1434 verification."""
1435
1436 partition = self.partition
1437 script.Print("Verifying %s..." % (partition,))
1438 ranges = self.tgt.care_map
1439 ranges_str = ranges.to_string_raw()
1440 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1441 'ui_print(" Verified.") || '
1442 'ui_print("\\"%s\\" has unexpected contents.");' % (
1443 self.device, ranges_str,
1444 self.tgt.TotalSha1(include_clobbered_blocks=True),
1445 self.device))
1446 script.AppendExtra("")
1447
Tao Baod522bdc2016-04-12 15:53:16 -07001448 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001449 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001450
1451 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001452 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001453 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001454
1455 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001456 else:
Tao Baod522bdc2016-04-12 15:53:16 -07001457 if touched_blocks_only and self.version >= 3:
1458 ranges = self.touched_src_ranges
1459 expected_sha1 = self.touched_src_sha1
1460 else:
1461 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1462 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001463
1464 # No blocks to be checked, skipping.
1465 if not ranges:
1466 return
1467
Tao Bao5ece99d2015-05-12 11:42:31 -07001468 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001469 if self.version >= 4:
1470 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1471 'block_image_verify("%s", '
1472 'package_extract_file("%s.transfer.list"), '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001473 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baod522bdc2016-04-12 15:53:16 -07001474 self.device, ranges_str, expected_sha1,
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001475 self.device, partition, partition, partition))
1476 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001477 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1478 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001479 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001480 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baod522bdc2016-04-12 15:53:16 -07001481 self.device, ranges_str, expected_sha1,
Sami Tolvanene09d0962015-04-24 11:54:01 +01001482 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001483 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001484 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001485 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001486 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001487 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001488
Tianjie Xufc3422a2015-12-15 11:53:59 -08001489 if self.version >= 4:
1490
1491 # Bug: 21124327
1492 # When generating incrementals for the system and vendor partitions in
1493 # version 4 or newer, explicitly check the first block (which contains
1494 # the superblock) of the partition to see if it's what we expect. If
1495 # this check fails, give an explicit log message about the partition
1496 # having been remounted R/W (the most likely explanation).
1497 if self.check_first_block:
1498 script.AppendExtra('check_first_block("%s");' % (self.device,))
1499
1500 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001501 if partition == "system":
1502 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1503 else:
1504 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001505 script.AppendExtra((
1506 'ifelse (block_image_recover("{device}", "{ranges}") && '
1507 'block_image_verify("{device}", '
1508 'package_extract_file("{partition}.transfer.list"), '
1509 '"{partition}.new.dat", "{partition}.patch.dat"), '
1510 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001511 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001512 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001513 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001514
Tao Baodd2a5892015-03-12 12:32:37 -07001515 # Abort the OTA update. Note that the incremental OTA cannot be applied
1516 # even if it may match the checksum of the target partition.
1517 # a) If version < 3, operations like move and erase will make changes
1518 # unconditionally and damage the partition.
1519 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001520 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001521 if partition == "system":
1522 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1523 else:
1524 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1525 script.AppendExtra((
1526 'abort("E%d: %s partition has unexpected contents");\n'
1527 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001528
Tao Bao5fcaaef2015-06-01 13:40:49 -07001529 def _WritePostInstallVerifyScript(self, script):
1530 partition = self.partition
1531 script.Print('Verifying the updated %s image...' % (partition,))
1532 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1533 ranges = self.tgt.care_map
1534 ranges_str = ranges.to_string_raw()
1535 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1536 self.device, ranges_str,
1537 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001538
1539 # Bug: 20881595
1540 # Verify that extended blocks are really zeroed out.
1541 if self.tgt.extended:
1542 ranges_str = self.tgt.extended.to_string_raw()
1543 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1544 self.device, ranges_str,
1545 self._HashZeroBlocks(self.tgt.extended.size())))
1546 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001547 if partition == "system":
1548 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1549 else:
1550 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001551 script.AppendExtra(
1552 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001553 ' abort("E%d: %s partition has unexpected non-zero contents after '
1554 'OTA update");\n'
1555 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001556 else:
1557 script.Print('Verified the updated %s image.' % (partition,))
1558
Tianjie Xu209db462016-05-24 17:34:52 -07001559 if partition == "system":
1560 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1561 else:
1562 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1563
Tao Bao5fcaaef2015-06-01 13:40:49 -07001564 script.AppendExtra(
1565 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001566 ' abort("E%d: %s partition has unexpected contents after OTA '
1567 'update");\n'
1568 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001569
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001570 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001571 ZipWrite(output_zip,
1572 '{}.transfer.list'.format(self.path),
1573 '{}.transfer.list'.format(self.partition))
1574 ZipWrite(output_zip,
1575 '{}.new.dat'.format(self.path),
1576 '{}.new.dat'.format(self.partition))
1577 ZipWrite(output_zip,
1578 '{}.patch.dat'.format(self.path),
1579 '{}.patch.dat'.format(self.partition),
1580 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001581
Tianjie Xu209db462016-05-24 17:34:52 -07001582 if self.partition == "system":
1583 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1584 else:
1585 code = ErrorCode.VENDOR_UPDATE_FAILURE
1586
Dan Albert8e0178d2015-01-27 15:53:15 -08001587 call = ('block_image_update("{device}", '
1588 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub2deb222016-03-25 15:01:33 -07001589 '"{partition}.new.dat", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001590 ' abort("E{code}: Failed to update {partition} image.");'.format(
1591 device=self.device, partition=self.partition, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001592 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001593
Dan Albert8b72aef2015-03-23 19:13:21 -07001594 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001595 data = source.ReadRangeSet(ranges)
1596 ctx = sha1()
1597
1598 for p in data:
1599 ctx.update(p)
1600
1601 return ctx.hexdigest()
1602
Tao Baoe9b61912015-07-09 17:37:49 -07001603 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1604 """Return the hash value for all zero blocks."""
1605 zero_block = '\x00' * 4096
1606 ctx = sha1()
1607 for _ in range(num_blocks):
1608 ctx.update(zero_block)
1609
1610 return ctx.hexdigest()
1611
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001612
1613DataImage = blockimgdiff.DataImage
1614
Doug Zongker96a57e72010-09-26 14:57:41 -07001615# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001616PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001617 "ext4": "EMMC",
1618 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001619 "f2fs": "EMMC",
1620 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001621}
Doug Zongker96a57e72010-09-26 14:57:41 -07001622
1623def GetTypeAndDevice(mount_point, info):
1624 fstab = info["fstab"]
1625 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001626 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1627 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001628 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001629 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001630
1631
1632def ParseCertificate(data):
1633 """Parse a PEM-format certificate."""
1634 cert = []
1635 save = False
1636 for line in data.split("\n"):
1637 if "--END CERTIFICATE--" in line:
1638 break
1639 if save:
1640 cert.append(line)
1641 if "--BEGIN CERTIFICATE--" in line:
1642 save = True
1643 cert = "".join(cert).decode('base64')
1644 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001645
Doug Zongker412c02f2014-02-13 10:58:24 -08001646def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1647 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001648 """Generate a binary patch that creates the recovery image starting
1649 with the boot image. (Most of the space in these images is just the
1650 kernel, which is identical for the two, so the resulting patch
1651 should be efficient.) Add it to the output zip, along with a shell
1652 script that is run from init.rc on first boot to actually do the
1653 patching and install the new recovery image.
1654
1655 recovery_img and boot_img should be File objects for the
1656 corresponding images. info should be the dictionary returned by
1657 common.LoadInfoDict() on the input target_files.
1658 """
1659
Doug Zongker412c02f2014-02-13 10:58:24 -08001660 if info_dict is None:
1661 info_dict = OPTIONS.info_dict
1662
Tao Baof2cffbd2015-07-22 12:33:18 -07001663 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001664 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001665
Tao Baof2cffbd2015-07-22 12:33:18 -07001666 if full_recovery_image:
1667 output_sink("etc/recovery.img", recovery_img.data)
1668
1669 else:
1670 diff_program = ["imgdiff"]
1671 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1672 if os.path.exists(path):
1673 diff_program.append("-b")
1674 diff_program.append(path)
1675 bonus_args = "-b /system/etc/recovery-resource.dat"
1676 else:
1677 bonus_args = ""
1678
1679 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1680 _, _, patch = d.ComputePatch()
1681 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001682
Dan Albertebb19aa2015-03-27 19:11:53 -07001683 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001684 # The following GetTypeAndDevice()s need to use the path in the target
1685 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001686 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1687 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1688 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001689 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001690
Tao Baof2cffbd2015-07-22 12:33:18 -07001691 if full_recovery_image:
1692 sh = """#!/system/bin/sh
1693if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1694 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"
1695else
1696 log -t recovery "Recovery image already installed"
1697fi
1698""" % {'type': recovery_type,
1699 'device': recovery_device,
1700 'sha1': recovery_img.sha1,
1701 'size': recovery_img.size}
1702 else:
1703 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001704if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1705 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"
1706else
1707 log -t recovery "Recovery image already installed"
1708fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001709""" % {'boot_size': boot_img.size,
1710 'boot_sha1': boot_img.sha1,
1711 'recovery_size': recovery_img.size,
1712 'recovery_sha1': recovery_img.sha1,
1713 'boot_type': boot_type,
1714 'boot_device': boot_device,
1715 'recovery_type': recovery_type,
1716 'recovery_device': recovery_device,
1717 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001718
1719 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001720 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001721 # target-files expects it to be, and put it there.
1722 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001723 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001724 if system_root_image:
1725 init_rc_dir = os.path.join(input_dir, "ROOT")
1726 else:
1727 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001728 init_rc_files = os.listdir(init_rc_dir)
1729 for init_rc_file in init_rc_files:
1730 if (not init_rc_file.startswith('init.') or
1731 not init_rc_file.endswith('.rc')):
1732 continue
1733
1734 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001735 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001736 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001737 if m:
1738 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001739 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001740 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001741
1742 if found:
1743 break
1744
Tao Bao89fbb0f2017-01-10 10:47:58 -08001745 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001746
1747 output_sink(sh_location, sh)