blob: 956f0004ae3eade917aa9578d0418ea2dd0e7dcf [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)
Tao Bao6cd54732017-02-27 15:12:05 -0800149
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700150 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:
Tao Bao6cd54732017-02-27 15:12:05 -0800153 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700154
Tao Bao6cd54732017-02-27 15:12:05 -0800155 assert "recovery_api_version" in d
Doug Zongker37974732010-09-16 17:44:38 -0700156
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800157 if "fstab_version" not in d:
158 d["fstab_version"] = "1"
159
Tao Bao84e75682015-07-19 02:38:53 -0700160 # A few properties are stored as links to the files in the out/ directory.
161 # It works fine with the build system. However, they are no longer available
162 # when (re)generating from target_files zip. If input_dir is not None, we
163 # are doing repacking. Redirect those properties to the actual files in the
164 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700165 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400166 # We carry a copy of file_contexts.bin under META/. If not available,
167 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700168 # to build images than the one running on device, such as when enabling
169 # system_root_image. In that case, we must have the one for image
170 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700171 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
172 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700173 if d.get("system_root_image") == "true":
174 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700175 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700176 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700177 if not os.path.exists(fc_config):
178 fc_config = None
179
180 if fc_config:
181 d["selinux_fc"] = fc_config
182
Tao Bao84e75682015-07-19 02:38:53 -0700183 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
184 if d.get("system_root_image") == "true":
185 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
186 d["ramdisk_fs_config"] = os.path.join(
187 input_dir, "META", "root_filesystem_config.txt")
188
Tao Baof54216f2016-03-29 15:12:37 -0700189 # Redirect {system,vendor}_base_fs_file.
190 if "system_base_fs_file" in d:
191 basename = os.path.basename(d["system_base_fs_file"])
192 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700193 if os.path.exists(system_base_fs_file):
194 d["system_base_fs_file"] = system_base_fs_file
195 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800196 print("Warning: failed to find system base fs file: %s" % (
197 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700198 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700199
200 if "vendor_base_fs_file" in d:
201 basename = os.path.basename(d["vendor_base_fs_file"])
202 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700203 if os.path.exists(vendor_base_fs_file):
204 d["vendor_base_fs_file"] = vendor_base_fs_file
205 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800206 print("Warning: failed to find vendor base fs file: %s" % (
207 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700208 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700209
Doug Zongker37974732010-09-16 17:44:38 -0700210 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800211 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700212 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700213 if not line:
214 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700215 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700216 if not value:
217 continue
Doug Zongker37974732010-09-16 17:44:38 -0700218 if name == "blocksize":
219 d[name] = value
220 else:
221 d[name + "_size"] = value
222 except KeyError:
223 pass
224
225 def makeint(key):
226 if key in d:
227 d[key] = int(d[key], 0)
228
229 makeint("recovery_api_version")
230 makeint("blocksize")
231 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700232 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700233 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700234 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700235 makeint("recovery_size")
236 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800237 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700238
Tianjie Xucfa86222016-03-07 16:31:19 -0800239 system_root_image = d.get("system_root_image", None) == "true"
240 if d.get("no_recovery", None) != "true":
241 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800242 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800243 recovery_fstab_path, system_root_image)
244 elif d.get("recovery_as_boot", None) == "true":
245 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
246 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
247 recovery_fstab_path, system_root_image)
248 else:
249 d["fstab"] = None
250
Doug Zongkerc9253822014-02-04 12:17:58 -0800251 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700252 return d
253
Doug Zongkerc9253822014-02-04 12:17:58 -0800254def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700255 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800256 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700257 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800258 print("Warning: could not find SYSTEM/build.prop in %s" % (zip,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700259 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700260 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700261
Michael Runge6e836112014-04-15 17:40:21 -0700262def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700263 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700264 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700265 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700266 if not line or line.startswith("#"):
267 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700268 if "=" in line:
269 name, value = line.split("=", 1)
270 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700271 return d
272
Tianjie Xucfa86222016-03-07 16:31:19 -0800273def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
274 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700275 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700276 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700277 self.mount_point = mount_point
278 self.fs_type = fs_type
279 self.device = device
280 self.length = length
281 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700282 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700283
284 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800285 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700286 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800287 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700288 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700289
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800290 if fstab_version == 1:
291 d = {}
292 for line in data.split("\n"):
293 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700294 if not line or line.startswith("#"):
295 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800296 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700297 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800298 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800299 options = None
300 if len(pieces) >= 4:
301 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700302 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800303 if len(pieces) >= 5:
304 options = pieces[4]
305 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700306 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800307 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800308 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700309 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700310
Dan Albert8b72aef2015-03-23 19:13:21 -0700311 mount_point = pieces[0]
312 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800313 if options:
314 options = options.split(",")
315 for i in options:
316 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700317 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800318 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800319 print("%s: unknown option \"%s\"" % (mount_point, i))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800320
Dan Albert8b72aef2015-03-23 19:13:21 -0700321 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
322 device=pieces[2], length=length,
323 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800324
325 elif fstab_version == 2:
326 d = {}
327 for line in data.split("\n"):
328 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700329 if not line or line.startswith("#"):
330 continue
Tao Bao548eb762015-06-10 12:32:41 -0700331 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800332 pieces = line.split()
333 if len(pieces) != 5:
334 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
335
336 # Ignore entries that are managed by vold
337 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700338 if "voldmanaged=" in options:
339 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800340
341 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700342 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800343 options = options.split(",")
344 for i in options:
345 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700346 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800347 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800348 # Ignore all unknown options in the unified fstab
349 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800350
Tao Bao548eb762015-06-10 12:32:41 -0700351 mount_flags = pieces[3]
352 # Honor the SELinux context if present.
353 context = None
354 for i in mount_flags.split(","):
355 if i.startswith("context="):
356 context = i
357
Dan Albert8b72aef2015-03-23 19:13:21 -0700358 mount_point = pieces[1]
359 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700360 device=pieces[0], length=length,
361 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800362
363 else:
364 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
365
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700366 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700367 # system. Other areas assume system is always at "/system" so point /system
368 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700369 if system_root_image:
370 assert not d.has_key("/system") and d.has_key("/")
371 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700372 return d
373
374
Doug Zongker37974732010-09-16 17:44:38 -0700375def DumpInfoDict(d):
376 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800377 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700378
Dan Albert8b72aef2015-03-23 19:13:21 -0700379
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400380def AppendAVBSigningArgs(cmd):
381 """Append signing arguments for avbtool."""
382 keypath = OPTIONS.info_dict.get("board_avb_key_path", None)
383 algorithm = OPTIONS.info_dict.get("board_avb_algorithm", None)
384 if not keypath or not algorithm:
385 algorithm = "SHA256_RSA4096"
386 keypath = "external/avb/test/data/testkey_rsa4096.pem"
387 cmd.extend(["--key", keypath, "--algorithm", algorithm])
388
389
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700390def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800391 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700392 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700393
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700394 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800395 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
396 we are building a two-step special image (i.e. building a recovery image to
397 be loaded into /boot in two-step OTAs).
398
399 Return the image data, or None if sourcedir does not appear to contains files
400 for building the requested image.
401 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700402
403 def make_ramdisk():
404 ramdisk_img = tempfile.NamedTemporaryFile()
405
406 if os.access(fs_config_file, os.F_OK):
407 cmd = ["mkbootfs", "-f", fs_config_file,
408 os.path.join(sourcedir, "RAMDISK")]
409 else:
410 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
411 p1 = Run(cmd, stdout=subprocess.PIPE)
412 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
413
414 p2.wait()
415 p1.wait()
416 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
417 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
418
419 return ramdisk_img
420
421 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
422 return None
423
424 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700425 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700426
Doug Zongkerd5131602012-08-02 14:46:42 -0700427 if info_dict is None:
428 info_dict = OPTIONS.info_dict
429
Doug Zongkereef39442009-04-02 12:14:19 -0700430 img = tempfile.NamedTemporaryFile()
431
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700432 if has_ramdisk:
433 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700434
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800435 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
436 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
437
438 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700439
Benoit Fradina45a8682014-07-14 21:00:43 +0200440 fn = os.path.join(sourcedir, "second")
441 if os.access(fn, os.F_OK):
442 cmd.append("--second")
443 cmd.append(fn)
444
Doug Zongker171f1cd2009-06-15 22:36:37 -0700445 fn = os.path.join(sourcedir, "cmdline")
446 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700447 cmd.append("--cmdline")
448 cmd.append(open(fn).read().rstrip("\n"))
449
450 fn = os.path.join(sourcedir, "base")
451 if os.access(fn, os.F_OK):
452 cmd.append("--base")
453 cmd.append(open(fn).read().rstrip("\n"))
454
Ying Wang4de6b5b2010-08-25 14:29:34 -0700455 fn = os.path.join(sourcedir, "pagesize")
456 if os.access(fn, os.F_OK):
457 cmd.append("--pagesize")
458 cmd.append(open(fn).read().rstrip("\n"))
459
Doug Zongkerd5131602012-08-02 14:46:42 -0700460 args = info_dict.get("mkbootimg_args", None)
461 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700462 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700463
Sami Tolvanen3303d902016-03-15 16:49:30 +0000464 args = info_dict.get("mkbootimg_version_args", None)
465 if args and args.strip():
466 cmd.extend(shlex.split(args))
467
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700468 if has_ramdisk:
469 cmd.extend(["--ramdisk", ramdisk_img.name])
470
Tao Baod95e9fd2015-03-29 23:07:41 -0700471 img_unsigned = None
472 if info_dict.get("vboot", None):
473 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700474 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700475 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700476 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700477
478 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700479 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700480 assert p.returncode == 0, "mkbootimg of %s image failed" % (
481 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700482
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100483 if (info_dict.get("boot_signer", None) == "true" and
484 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800485 # Hard-code the path as "/boot" for two-step special recovery image (which
486 # will be loaded into /boot during the two-step OTA).
487 if two_step_image:
488 path = "/boot"
489 else:
490 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700491 cmd = [OPTIONS.boot_signer_path]
492 cmd.extend(OPTIONS.boot_signer_args)
493 cmd.extend([path, img.name,
494 info_dict["verity_key"] + ".pk8",
495 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700496 p = Run(cmd, stdout=subprocess.PIPE)
497 p.communicate()
498 assert p.returncode == 0, "boot_signer of %s image failed" % path
499
Tao Baod95e9fd2015-03-29 23:07:41 -0700500 # Sign the image if vboot is non-empty.
501 elif info_dict.get("vboot", None):
502 path = "/" + os.path.basename(sourcedir).lower()
503 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800504 # We have switched from the prebuilt futility binary to using the tool
505 # (futility-host) built from the source. Override the setting in the old
506 # TF.zip.
507 futility = info_dict["futility"]
508 if futility.startswith("prebuilts/"):
509 futility = "futility-host"
510 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700511 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700512 info_dict["vboot_key"] + ".vbprivk",
513 info_dict["vboot_subkey"] + ".vbprivk",
514 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700515 img.name]
516 p = Run(cmd, stdout=subprocess.PIPE)
517 p.communicate()
518 assert p.returncode == 0, "vboot_signer of %s image failed" % path
519
Tao Baof3282b42015-04-01 11:21:55 -0700520 # Clean up the temp files.
521 img_unsigned.close()
522 img_keyblock.close()
523
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400524 # AVB: if enabled, calculate and add hash to boot.img.
Tao Baob31b94e2016-09-29 21:59:06 -0700525 if info_dict.get("board_avb_enable", None) == "true":
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400526 avbtool = os.getenv('AVBTOOL') or "avbtool"
Tao Baob31b94e2016-09-29 21:59:06 -0700527 part_size = info_dict.get("boot_size", None)
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400528 cmd = [avbtool, "add_hash_footer", "--image", img.name,
529 "--partition_size", str(part_size), "--partition_name", "boot"]
530 AppendAVBSigningArgs(cmd)
Tao Baob31b94e2016-09-29 21:59:06 -0700531 args = info_dict.get("board_avb_boot_add_hash_footer_args", None)
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400532 if args and args.strip():
533 cmd.extend(shlex.split(args))
534 p = Run(cmd, stdout=subprocess.PIPE)
535 p.communicate()
536 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
537 os.path.basename(OPTIONS.input_tmp))
David Zeuthend995f4b2016-01-29 16:59:17 -0500538
539 img.seek(os.SEEK_SET, 0)
540 data = img.read()
541
542 if has_ramdisk:
543 ramdisk_img.close()
544 img.close()
545
546 return data
547
548
Doug Zongkerd5131602012-08-02 14:46:42 -0700549def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800550 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700551 """Return a File object with the desired bootable image.
552
553 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
554 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
555 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700556
Doug Zongker55d93282011-01-25 17:03:34 -0800557 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
558 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800559 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800560 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700561
562 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
563 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800564 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700565 return File.FromLocalFile(name, prebuilt_path)
566
Tao Bao89fbb0f2017-01-10 10:47:58 -0800567 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700568
569 if info_dict is None:
570 info_dict = OPTIONS.info_dict
571
572 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800573 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
574 # for recovery.
575 has_ramdisk = (info_dict.get("system_root_image") != "true" or
576 prebuilt_name != "boot.img" or
577 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700578
Doug Zongker6f1d0312014-08-22 08:07:12 -0700579 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400580 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
581 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800582 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700583 if data:
584 return File(name, data)
585 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800586
Doug Zongkereef39442009-04-02 12:14:19 -0700587
Doug Zongker75f17362009-12-08 13:46:44 -0800588def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800589 """Unzip the given archive into a temporary directory and return the name.
590
591 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
592 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
593
594 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
595 main file), open for reading.
596 """
Doug Zongkereef39442009-04-02 12:14:19 -0700597
598 tmp = tempfile.mkdtemp(prefix="targetfiles-")
599 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800600
601 def unzip_to_dir(filename, dirname):
602 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
603 if pattern is not None:
604 cmd.append(pattern)
605 p = Run(cmd, stdout=subprocess.PIPE)
606 p.communicate()
607 if p.returncode != 0:
608 raise ExternalError("failed to unzip input target-files \"%s\"" %
609 (filename,))
610
611 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
612 if m:
613 unzip_to_dir(m.group(1), tmp)
614 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
615 filename = m.group(1)
616 else:
617 unzip_to_dir(filename, tmp)
618
619 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700620
621
622def GetKeyPasswords(keylist):
623 """Given a list of keys, prompt the user to enter passwords for
624 those which require them. Return a {key: password} dict. password
625 will be None if the key has no password."""
626
Doug Zongker8ce7c252009-05-22 13:34:54 -0700627 no_passwords = []
628 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700629 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700630 devnull = open("/dev/null", "w+b")
631 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800632 # We don't need a password for things that aren't really keys.
633 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700634 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700635 continue
636
T.R. Fullhart37e10522013-03-18 10:31:26 -0700637 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700638 "-inform", "DER", "-nocrypt"],
639 stdin=devnull.fileno(),
640 stdout=devnull.fileno(),
641 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700642 p.communicate()
643 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700644 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700645 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700646 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700647 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
648 "-inform", "DER", "-passin", "pass:"],
649 stdin=devnull.fileno(),
650 stdout=devnull.fileno(),
651 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700652 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700653 if p.returncode == 0:
654 # Encrypted key with empty string as password.
655 key_passwords[k] = ''
656 elif stderr.startswith('Error decrypting key'):
657 # Definitely encrypted key.
658 # It would have said "Error reading key" if it didn't parse correctly.
659 need_passwords.append(k)
660 else:
661 # Potentially, a type of key that openssl doesn't understand.
662 # We'll let the routines in signapk.jar handle it.
663 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700664 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700665
T.R. Fullhart37e10522013-03-18 10:31:26 -0700666 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700667 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700668 return key_passwords
669
670
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800671def GetMinSdkVersion(apk_name):
672 """Get the minSdkVersion delared in the APK. This can be both a decimal number
673 (API Level) or a codename.
674 """
675
676 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
677 output, err = p.communicate()
678 if err:
679 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
680 % (p.returncode,))
681
682 for line in output.split("\n"):
683 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
684 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
685 if m:
686 return m.group(1)
687 raise ExternalError("No minSdkVersion returned by aapt")
688
689
690def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
691 """Get the minSdkVersion declared in the APK as a number (API Level). If
692 minSdkVersion is set to a codename, it is translated to a number using the
693 provided map.
694 """
695
696 version = GetMinSdkVersion(apk_name)
697 try:
698 return int(version)
699 except ValueError:
700 # Not a decimal number. Codename?
701 if version in codename_to_api_level_map:
702 return codename_to_api_level_map[version]
703 else:
704 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
705 % (version, codename_to_api_level_map))
706
707
708def SignFile(input_name, output_name, key, password, min_api_level=None,
709 codename_to_api_level_map=dict(),
710 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700711 """Sign the input_name zip/jar/apk, producing output_name. Use the
712 given key and password (the latter may be None if the key does not
713 have a password.
714
Doug Zongker951495f2009-08-14 12:44:19 -0700715 If whole_file is true, use the "-w" option to SignApk to embed a
716 signature that covers the whole file in the archive comment of the
717 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800718
719 min_api_level is the API Level (int) of the oldest platform this file may end
720 up on. If not specified for an APK, the API Level is obtained by interpreting
721 the minSdkVersion attribute of the APK's AndroidManifest.xml.
722
723 codename_to_api_level_map is needed to translate the codename which may be
724 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700725 """
Doug Zongker951495f2009-08-14 12:44:19 -0700726
Alex Klyubin9667b182015-12-10 13:38:50 -0800727 java_library_path = os.path.join(
728 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
729
Tao Baoe95540e2016-11-08 12:08:53 -0800730 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
731 ["-Djava.library.path=" + java_library_path,
732 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
733 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700734 if whole_file:
735 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800736
737 min_sdk_version = min_api_level
738 if min_sdk_version is None:
739 if not whole_file:
740 min_sdk_version = GetMinSdkVersionInt(
741 input_name, codename_to_api_level_map)
742 if min_sdk_version is not None:
743 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
744
T.R. Fullhart37e10522013-03-18 10:31:26 -0700745 cmd.extend([key + OPTIONS.public_key_suffix,
746 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800747 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700748
749 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700750 if password is not None:
751 password += "\n"
752 p.communicate(password)
753 if p.returncode != 0:
754 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
755
Doug Zongkereef39442009-04-02 12:14:19 -0700756
Doug Zongker37974732010-09-16 17:44:38 -0700757def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700758 """Check the data string passed against the max size limit, if
759 any, for the given target. Raise exception if the data is too big.
760 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700761
Dan Albert8b72aef2015-03-23 19:13:21 -0700762 if target.endswith(".img"):
763 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700764 mount_point = "/" + target
765
Ying Wangf8824af2014-06-03 14:07:27 -0700766 fs_type = None
767 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700768 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700769 if mount_point == "/userdata":
770 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700771 p = info_dict["fstab"][mount_point]
772 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800773 device = p.device
774 if "/" in device:
775 device = device[device.rfind("/")+1:]
776 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700777 if not fs_type or not limit:
778 return
Doug Zongkereef39442009-04-02 12:14:19 -0700779
Andrew Boie0f9aec82012-02-14 09:32:52 -0800780 size = len(data)
781 pct = float(size) * 100.0 / limit
782 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
783 if pct >= 99.0:
784 raise ExternalError(msg)
785 elif pct >= 95.0:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800786 print("\n WARNING: %s\n" % (msg,))
Andrew Boie0f9aec82012-02-14 09:32:52 -0800787 elif OPTIONS.verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800788 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700789
790
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800791def ReadApkCerts(tf_zip):
792 """Given a target_files ZipFile, parse the META/apkcerts.txt file
793 and return a {package: cert} dict."""
794 certmap = {}
795 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
796 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700797 if not line:
798 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800799 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
800 r'private_key="(.*)"$', line)
801 if m:
802 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700803 public_key_suffix_len = len(OPTIONS.public_key_suffix)
804 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800805 if cert in SPECIAL_CERT_STRINGS and not privkey:
806 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700807 elif (cert.endswith(OPTIONS.public_key_suffix) and
808 privkey.endswith(OPTIONS.private_key_suffix) and
809 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
810 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800811 else:
812 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
813 return certmap
814
815
Doug Zongkereef39442009-04-02 12:14:19 -0700816COMMON_DOCSTRING = """
817 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700818 Prepend <dir>/bin to the list of places to search for binaries
819 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700820
Doug Zongker05d3dea2009-06-22 11:32:31 -0700821 -s (--device_specific) <file>
822 Path to the python module containing device-specific
823 releasetools code.
824
Doug Zongker8bec09e2009-11-30 15:37:14 -0800825 -x (--extra) <key=value>
826 Add a key/value pair to the 'extras' dict, which device-specific
827 extension code may look at.
828
Doug Zongkereef39442009-04-02 12:14:19 -0700829 -v (--verbose)
830 Show command lines being executed.
831
832 -h (--help)
833 Display this usage message and exit.
834"""
835
836def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800837 print(docstring.rstrip("\n"))
838 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700839
840
841def ParseOptions(argv,
842 docstring,
843 extra_opts="", extra_long_opts=(),
844 extra_option_handler=None):
845 """Parse the options in argv and return any arguments that aren't
846 flags. docstring is the calling module's docstring, to be displayed
847 for errors and -h. extra_opts and extra_long_opts are for flags
848 defined by the caller, which are processed by passing them to
849 extra_option_handler."""
850
851 try:
852 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800853 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800854 ["help", "verbose", "path=", "signapk_path=",
855 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700856 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700857 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
858 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800859 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700860 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700861 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700862 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800863 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700864 sys.exit(2)
865
Doug Zongkereef39442009-04-02 12:14:19 -0700866 for o, a in opts:
867 if o in ("-h", "--help"):
868 Usage(docstring)
869 sys.exit()
870 elif o in ("-v", "--verbose"):
871 OPTIONS.verbose = True
872 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700873 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700874 elif o in ("--signapk_path",):
875 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800876 elif o in ("--signapk_shared_library_path",):
877 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700878 elif o in ("--extra_signapk_args",):
879 OPTIONS.extra_signapk_args = shlex.split(a)
880 elif o in ("--java_path",):
881 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700882 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800883 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700884 elif o in ("--public_key_suffix",):
885 OPTIONS.public_key_suffix = a
886 elif o in ("--private_key_suffix",):
887 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800888 elif o in ("--boot_signer_path",):
889 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700890 elif o in ("--boot_signer_args",):
891 OPTIONS.boot_signer_args = shlex.split(a)
892 elif o in ("--verity_signer_path",):
893 OPTIONS.verity_signer_path = a
894 elif o in ("--verity_signer_args",):
895 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700896 elif o in ("-s", "--device_specific"):
897 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800898 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800899 key, value = a.split("=", 1)
900 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700901 else:
902 if extra_option_handler is None or not extra_option_handler(o, a):
903 assert False, "unknown option \"%s\"" % (o,)
904
Doug Zongker85448772014-09-09 14:59:20 -0700905 if OPTIONS.search_path:
906 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
907 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700908
909 return args
910
911
Doug Zongkerfc44a512014-08-26 13:10:25 -0700912def MakeTempFile(prefix=None, suffix=None):
913 """Make a temp file and add it to the list of things to be deleted
914 when Cleanup() is called. Return the filename."""
915 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
916 os.close(fd)
917 OPTIONS.tempfiles.append(fn)
918 return fn
919
920
Doug Zongkereef39442009-04-02 12:14:19 -0700921def Cleanup():
922 for i in OPTIONS.tempfiles:
923 if os.path.isdir(i):
924 shutil.rmtree(i)
925 else:
926 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700927
928
929class PasswordManager(object):
930 def __init__(self):
931 self.editor = os.getenv("EDITOR", None)
932 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
933
934 def GetPasswords(self, items):
935 """Get passwords corresponding to each string in 'items',
936 returning a dict. (The dict may have keys in addition to the
937 values in 'items'.)
938
939 Uses the passwords in $ANDROID_PW_FILE if available, letting the
940 user edit that file to add more needed passwords. If no editor is
941 available, or $ANDROID_PW_FILE isn't define, prompts the user
942 interactively in the ordinary way.
943 """
944
945 current = self.ReadFile()
946
947 first = True
948 while True:
949 missing = []
950 for i in items:
951 if i not in current or not current[i]:
952 missing.append(i)
953 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700954 if not missing:
955 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700956
957 for i in missing:
958 current[i] = ""
959
960 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800961 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700962 answer = raw_input("try to edit again? [y]> ").strip()
963 if answer and answer[0] not in 'yY':
964 raise RuntimeError("key passwords unavailable")
965 first = False
966
967 current = self.UpdateAndReadFile(current)
968
Dan Albert8b72aef2015-03-23 19:13:21 -0700969 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700970 """Prompt the user to enter a value (password) for each key in
971 'current' whose value is fales. Returns a new dict with all the
972 values.
973 """
974 result = {}
975 for k, v in sorted(current.iteritems()):
976 if v:
977 result[k] = v
978 else:
979 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700980 result[k] = getpass.getpass(
981 "Enter password for %s key> " % k).strip()
982 if result[k]:
983 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700984 return result
985
986 def UpdateAndReadFile(self, current):
987 if not self.editor or not self.pwfile:
988 return self.PromptResult(current)
989
990 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700991 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700992 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
993 f.write("# (Additional spaces are harmless.)\n\n")
994
995 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700996 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
997 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700998 f.write("[[[ %s ]]] %s\n" % (v, k))
999 if not v and first_line is None:
1000 # position cursor on first line with no password.
1001 first_line = i + 4
1002 f.close()
1003
1004 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1005 _, _ = p.communicate()
1006
1007 return self.ReadFile()
1008
1009 def ReadFile(self):
1010 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001011 if self.pwfile is None:
1012 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001013 try:
1014 f = open(self.pwfile, "r")
1015 for line in f:
1016 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001017 if not line or line[0] == '#':
1018 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001019 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1020 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001021 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001022 else:
1023 result[m.group(2)] = m.group(1)
1024 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001025 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001026 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001027 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001028 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001029
1030
Dan Albert8e0178d2015-01-27 15:53:15 -08001031def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1032 compress_type=None):
1033 import datetime
1034
1035 # http://b/18015246
1036 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1037 # for files larger than 2GiB. We can work around this by adjusting their
1038 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1039 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1040 # it isn't clear to me exactly what circumstances cause this).
1041 # `zipfile.write()` must be used directly to work around this.
1042 #
1043 # This mess can be avoided if we port to python3.
1044 saved_zip64_limit = zipfile.ZIP64_LIMIT
1045 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1046
1047 if compress_type is None:
1048 compress_type = zip_file.compression
1049 if arcname is None:
1050 arcname = filename
1051
1052 saved_stat = os.stat(filename)
1053
1054 try:
1055 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1056 # file to be zipped and reset it when we're done.
1057 os.chmod(filename, perms)
1058
1059 # Use a fixed timestamp so the output is repeatable.
1060 epoch = datetime.datetime.fromtimestamp(0)
1061 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1062 os.utime(filename, (timestamp, timestamp))
1063
1064 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1065 finally:
1066 os.chmod(filename, saved_stat.st_mode)
1067 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1068 zipfile.ZIP64_LIMIT = saved_zip64_limit
1069
1070
Tao Bao58c1b962015-05-20 09:32:18 -07001071def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001072 compress_type=None):
1073 """Wrap zipfile.writestr() function to work around the zip64 limit.
1074
1075 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1076 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1077 when calling crc32(bytes).
1078
1079 But it still works fine to write a shorter string into a large zip file.
1080 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1081 when we know the string won't be too long.
1082 """
1083
1084 saved_zip64_limit = zipfile.ZIP64_LIMIT
1085 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1086
1087 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1088 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001089 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001090 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001091 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001092 else:
Tao Baof3282b42015-04-01 11:21:55 -07001093 zinfo = zinfo_or_arcname
1094
1095 # If compress_type is given, it overrides the value in zinfo.
1096 if compress_type is not None:
1097 zinfo.compress_type = compress_type
1098
Tao Bao58c1b962015-05-20 09:32:18 -07001099 # If perms is given, it has a priority.
1100 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001101 # If perms doesn't set the file type, mark it as a regular file.
1102 if perms & 0o770000 == 0:
1103 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001104 zinfo.external_attr = perms << 16
1105
Tao Baof3282b42015-04-01 11:21:55 -07001106 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001107 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1108
Dan Albert8b72aef2015-03-23 19:13:21 -07001109 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001110 zipfile.ZIP64_LIMIT = saved_zip64_limit
1111
1112
1113def ZipClose(zip_file):
1114 # http://b/18015246
1115 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1116 # central directory.
1117 saved_zip64_limit = zipfile.ZIP64_LIMIT
1118 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1119
1120 zip_file.close()
1121
1122 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001123
1124
1125class DeviceSpecificParams(object):
1126 module = None
1127 def __init__(self, **kwargs):
1128 """Keyword arguments to the constructor become attributes of this
1129 object, which is passed to all functions in the device-specific
1130 module."""
1131 for k, v in kwargs.iteritems():
1132 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001133 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001134
1135 if self.module is None:
1136 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001137 if not path:
1138 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001139 try:
1140 if os.path.isdir(path):
1141 info = imp.find_module("releasetools", [path])
1142 else:
1143 d, f = os.path.split(path)
1144 b, x = os.path.splitext(f)
1145 if x == ".py":
1146 f = b
1147 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001148 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001149 self.module = imp.load_module("device_specific", *info)
1150 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001151 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001152
1153 def _DoCall(self, function_name, *args, **kwargs):
1154 """Call the named function in the device-specific module, passing
1155 the given args and kwargs. The first argument to the call will be
1156 the DeviceSpecific object itself. If there is no module, or the
1157 module does not define the function, return the value of the
1158 'default' kwarg (which itself defaults to None)."""
1159 if self.module is None or not hasattr(self.module, function_name):
1160 return kwargs.get("default", None)
1161 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1162
1163 def FullOTA_Assertions(self):
1164 """Called after emitting the block of assertions at the top of a
1165 full OTA package. Implementations can add whatever additional
1166 assertions they like."""
1167 return self._DoCall("FullOTA_Assertions")
1168
Doug Zongkere5ff5902012-01-17 10:55:37 -08001169 def FullOTA_InstallBegin(self):
1170 """Called at the start of full OTA installation."""
1171 return self._DoCall("FullOTA_InstallBegin")
1172
Doug Zongker05d3dea2009-06-22 11:32:31 -07001173 def FullOTA_InstallEnd(self):
1174 """Called at the end of full OTA installation; typically this is
1175 used to install the image for the device's baseband processor."""
1176 return self._DoCall("FullOTA_InstallEnd")
1177
1178 def IncrementalOTA_Assertions(self):
1179 """Called after emitting the block of assertions at the top of an
1180 incremental OTA package. Implementations can add whatever
1181 additional assertions they like."""
1182 return self._DoCall("IncrementalOTA_Assertions")
1183
Doug Zongkere5ff5902012-01-17 10:55:37 -08001184 def IncrementalOTA_VerifyBegin(self):
1185 """Called at the start of the verification phase of incremental
1186 OTA installation; additional checks can be placed here to abort
1187 the script before any changes are made."""
1188 return self._DoCall("IncrementalOTA_VerifyBegin")
1189
Doug Zongker05d3dea2009-06-22 11:32:31 -07001190 def IncrementalOTA_VerifyEnd(self):
1191 """Called at the end of the verification phase of incremental OTA
1192 installation; additional checks can be placed here to abort the
1193 script before any changes are made."""
1194 return self._DoCall("IncrementalOTA_VerifyEnd")
1195
Doug Zongkere5ff5902012-01-17 10:55:37 -08001196 def IncrementalOTA_InstallBegin(self):
1197 """Called at the start of incremental OTA installation (after
1198 verification is complete)."""
1199 return self._DoCall("IncrementalOTA_InstallBegin")
1200
Doug Zongker05d3dea2009-06-22 11:32:31 -07001201 def IncrementalOTA_InstallEnd(self):
1202 """Called at the end of incremental OTA installation; typically
1203 this is used to install the image for the device's baseband
1204 processor."""
1205 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001206
Tao Bao9bc6bb22015-11-09 16:58:28 -08001207 def VerifyOTA_Assertions(self):
1208 return self._DoCall("VerifyOTA_Assertions")
1209
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001210class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001211 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001212 self.name = name
1213 self.data = data
1214 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001215 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001216 self.sha1 = sha1(data).hexdigest()
1217
1218 @classmethod
1219 def FromLocalFile(cls, name, diskname):
1220 f = open(diskname, "rb")
1221 data = f.read()
1222 f.close()
1223 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001224
1225 def WriteToTemp(self):
1226 t = tempfile.NamedTemporaryFile()
1227 t.write(self.data)
1228 t.flush()
1229 return t
1230
Geremy Condra36bd3652014-02-06 19:45:10 -08001231 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001232 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001233
1234DIFF_PROGRAM_BY_EXT = {
1235 ".gz" : "imgdiff",
1236 ".zip" : ["imgdiff", "-z"],
1237 ".jar" : ["imgdiff", "-z"],
1238 ".apk" : ["imgdiff", "-z"],
1239 ".img" : "imgdiff",
1240 }
1241
1242class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001243 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001244 self.tf = tf
1245 self.sf = sf
1246 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001247 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001248
1249 def ComputePatch(self):
1250 """Compute the patch (as a string of data) needed to turn sf into
1251 tf. Returns the same tuple as GetPatch()."""
1252
1253 tf = self.tf
1254 sf = self.sf
1255
Doug Zongker24cd2802012-08-14 16:36:15 -07001256 if self.diff_program:
1257 diff_program = self.diff_program
1258 else:
1259 ext = os.path.splitext(tf.name)[1]
1260 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001261
1262 ttemp = tf.WriteToTemp()
1263 stemp = sf.WriteToTemp()
1264
1265 ext = os.path.splitext(tf.name)[1]
1266
1267 try:
1268 ptemp = tempfile.NamedTemporaryFile()
1269 if isinstance(diff_program, list):
1270 cmd = copy.copy(diff_program)
1271 else:
1272 cmd = [diff_program]
1273 cmd.append(stemp.name)
1274 cmd.append(ttemp.name)
1275 cmd.append(ptemp.name)
1276 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001277 err = []
1278 def run():
1279 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001280 if e:
1281 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001282 th = threading.Thread(target=run)
1283 th.start()
1284 th.join(timeout=300) # 5 mins
1285 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001286 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001287 p.terminate()
1288 th.join(5)
1289 if th.is_alive():
1290 p.kill()
1291 th.join()
1292
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001293 if err or p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001294 print("WARNING: failure running %s:\n%s\n" % (
1295 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001296 self.patch = None
1297 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001298 diff = ptemp.read()
1299 finally:
1300 ptemp.close()
1301 stemp.close()
1302 ttemp.close()
1303
1304 self.patch = diff
1305 return self.tf, self.sf, self.patch
1306
1307
1308 def GetPatch(self):
1309 """Return a tuple (target_file, source_file, patch_data).
1310 patch_data may be None if ComputePatch hasn't been called, or if
1311 computing the patch failed."""
1312 return self.tf, self.sf, self.patch
1313
1314
1315def ComputeDifferences(diffs):
1316 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001317 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001318
1319 # Do the largest files first, to try and reduce the long-pole effect.
1320 by_size = [(i.tf.size, i) for i in diffs]
1321 by_size.sort(reverse=True)
1322 by_size = [i[1] for i in by_size]
1323
1324 lock = threading.Lock()
1325 diff_iter = iter(by_size) # accessed under lock
1326
1327 def worker():
1328 try:
1329 lock.acquire()
1330 for d in diff_iter:
1331 lock.release()
1332 start = time.time()
1333 d.ComputePatch()
1334 dur = time.time() - start
1335 lock.acquire()
1336
1337 tf, sf, patch = d.GetPatch()
1338 if sf.name == tf.name:
1339 name = tf.name
1340 else:
1341 name = "%s (%s)" % (tf.name, sf.name)
1342 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001343 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001344 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001345 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1346 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001347 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001348 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001349 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001350 raise
1351
1352 # start worker threads; wait for them all to finish.
1353 threads = [threading.Thread(target=worker)
1354 for i in range(OPTIONS.worker_threads)]
1355 for th in threads:
1356 th.start()
1357 while threads:
1358 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001359
1360
Dan Albert8b72aef2015-03-23 19:13:21 -07001361class BlockDifference(object):
1362 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001363 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001364 self.tgt = tgt
1365 self.src = src
1366 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001367 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001368 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001369
Tao Baodd2a5892015-03-12 12:32:37 -07001370 if version is None:
1371 version = 1
1372 if OPTIONS.info_dict:
1373 version = max(
1374 int(i) for i in
1375 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1376 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001377
1378 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001379 version=self.version,
1380 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001381 tmpdir = tempfile.mkdtemp()
1382 OPTIONS.tempfiles.append(tmpdir)
1383 self.path = os.path.join(tmpdir, partition)
1384 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001385 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001386 self.touched_src_ranges = b.touched_src_ranges
1387 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001388
Tao Baoaac4ad52015-10-16 15:26:34 -07001389 if src is None:
1390 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1391 else:
1392 _, self.device = GetTypeAndDevice("/" + partition,
1393 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001394
Tao Baod8d14be2016-02-04 14:26:02 -08001395 @property
1396 def required_cache(self):
1397 return self._required_cache
1398
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001399 def WriteScript(self, script, output_zip, progress=None):
1400 if not self.src:
1401 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001402 script.Print("Patching %s image unconditionally..." % (self.partition,))
1403 else:
1404 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001405
Dan Albert8b72aef2015-03-23 19:13:21 -07001406 if progress:
1407 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001408 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001409 if OPTIONS.verify:
1410 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001411
Tao Bao9bc6bb22015-11-09 16:58:28 -08001412 def WriteStrictVerifyScript(self, script):
1413 """Verify all the blocks in the care_map, including clobbered blocks.
1414
1415 This differs from the WriteVerifyScript() function: a) it prints different
1416 error messages; b) it doesn't allow half-way updated images to pass the
1417 verification."""
1418
1419 partition = self.partition
1420 script.Print("Verifying %s..." % (partition,))
1421 ranges = self.tgt.care_map
1422 ranges_str = ranges.to_string_raw()
1423 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1424 'ui_print(" Verified.") || '
1425 'ui_print("\\"%s\\" has unexpected contents.");' % (
1426 self.device, ranges_str,
1427 self.tgt.TotalSha1(include_clobbered_blocks=True),
1428 self.device))
1429 script.AppendExtra("")
1430
Tao Baod522bdc2016-04-12 15:53:16 -07001431 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001432 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001433
1434 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001435 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001436 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001437
1438 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001439 else:
Tao Baod522bdc2016-04-12 15:53:16 -07001440 if touched_blocks_only and self.version >= 3:
1441 ranges = self.touched_src_ranges
1442 expected_sha1 = self.touched_src_sha1
1443 else:
1444 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1445 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001446
1447 # No blocks to be checked, skipping.
1448 if not ranges:
1449 return
1450
Tao Bao5ece99d2015-05-12 11:42:31 -07001451 ranges_str = ranges.to_string_raw()
Tao Bao9beea2a2017-02-28 19:15:21 -08001452 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001453 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1454 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001455 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001456 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baod522bdc2016-04-12 15:53:16 -07001457 self.device, ranges_str, expected_sha1,
Sami Tolvanene09d0962015-04-24 11:54:01 +01001458 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001459 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001460 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001461 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001462 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001463 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001464
Tianjie Xufc3422a2015-12-15 11:53:59 -08001465 if self.version >= 4:
1466
1467 # Bug: 21124327
1468 # When generating incrementals for the system and vendor partitions in
1469 # version 4 or newer, explicitly check the first block (which contains
1470 # the superblock) of the partition to see if it's what we expect. If
1471 # this check fails, give an explicit log message about the partition
1472 # having been remounted R/W (the most likely explanation).
1473 if self.check_first_block:
1474 script.AppendExtra('check_first_block("%s");' % (self.device,))
1475
1476 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001477 if partition == "system":
1478 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1479 else:
1480 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001481 script.AppendExtra((
1482 'ifelse (block_image_recover("{device}", "{ranges}") && '
1483 'block_image_verify("{device}", '
1484 'package_extract_file("{partition}.transfer.list"), '
1485 '"{partition}.new.dat", "{partition}.patch.dat"), '
1486 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001487 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001488 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001489 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001490
Tao Baodd2a5892015-03-12 12:32:37 -07001491 # Abort the OTA update. Note that the incremental OTA cannot be applied
1492 # even if it may match the checksum of the target partition.
1493 # a) If version < 3, operations like move and erase will make changes
1494 # unconditionally and damage the partition.
1495 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001496 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001497 if partition == "system":
1498 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1499 else:
1500 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1501 script.AppendExtra((
1502 'abort("E%d: %s partition has unexpected contents");\n'
1503 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001504
Tao Bao5fcaaef2015-06-01 13:40:49 -07001505 def _WritePostInstallVerifyScript(self, script):
1506 partition = self.partition
1507 script.Print('Verifying the updated %s image...' % (partition,))
1508 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1509 ranges = self.tgt.care_map
1510 ranges_str = ranges.to_string_raw()
1511 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1512 self.device, ranges_str,
1513 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001514
1515 # Bug: 20881595
1516 # Verify that extended blocks are really zeroed out.
1517 if self.tgt.extended:
1518 ranges_str = self.tgt.extended.to_string_raw()
1519 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1520 self.device, ranges_str,
1521 self._HashZeroBlocks(self.tgt.extended.size())))
1522 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001523 if partition == "system":
1524 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1525 else:
1526 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001527 script.AppendExtra(
1528 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001529 ' abort("E%d: %s partition has unexpected non-zero contents after '
1530 'OTA update");\n'
1531 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001532 else:
1533 script.Print('Verified the updated %s image.' % (partition,))
1534
Tianjie Xu209db462016-05-24 17:34:52 -07001535 if partition == "system":
1536 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1537 else:
1538 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1539
Tao Bao5fcaaef2015-06-01 13:40:49 -07001540 script.AppendExtra(
1541 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001542 ' abort("E%d: %s partition has unexpected contents after OTA '
1543 'update");\n'
1544 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001545
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001546 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001547 ZipWrite(output_zip,
1548 '{}.transfer.list'.format(self.path),
1549 '{}.transfer.list'.format(self.partition))
1550 ZipWrite(output_zip,
1551 '{}.new.dat'.format(self.path),
1552 '{}.new.dat'.format(self.partition))
1553 ZipWrite(output_zip,
1554 '{}.patch.dat'.format(self.path),
1555 '{}.patch.dat'.format(self.partition),
1556 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001557
Tianjie Xu209db462016-05-24 17:34:52 -07001558 if self.partition == "system":
1559 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1560 else:
1561 code = ErrorCode.VENDOR_UPDATE_FAILURE
1562
Dan Albert8e0178d2015-01-27 15:53:15 -08001563 call = ('block_image_update("{device}", '
1564 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub2deb222016-03-25 15:01:33 -07001565 '"{partition}.new.dat", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001566 ' abort("E{code}: Failed to update {partition} image.");'.format(
1567 device=self.device, partition=self.partition, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001568 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001569
Dan Albert8b72aef2015-03-23 19:13:21 -07001570 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001571 data = source.ReadRangeSet(ranges)
1572 ctx = sha1()
1573
1574 for p in data:
1575 ctx.update(p)
1576
1577 return ctx.hexdigest()
1578
Tao Baoe9b61912015-07-09 17:37:49 -07001579 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1580 """Return the hash value for all zero blocks."""
1581 zero_block = '\x00' * 4096
1582 ctx = sha1()
1583 for _ in range(num_blocks):
1584 ctx.update(zero_block)
1585
1586 return ctx.hexdigest()
1587
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001588
1589DataImage = blockimgdiff.DataImage
1590
Doug Zongker96a57e72010-09-26 14:57:41 -07001591# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001592PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001593 "ext4": "EMMC",
1594 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001595 "f2fs": "EMMC",
1596 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001597}
Doug Zongker96a57e72010-09-26 14:57:41 -07001598
1599def GetTypeAndDevice(mount_point, info):
1600 fstab = info["fstab"]
1601 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001602 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1603 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001604 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001605 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001606
1607
1608def ParseCertificate(data):
1609 """Parse a PEM-format certificate."""
1610 cert = []
1611 save = False
1612 for line in data.split("\n"):
1613 if "--END CERTIFICATE--" in line:
1614 break
1615 if save:
1616 cert.append(line)
1617 if "--BEGIN CERTIFICATE--" in line:
1618 save = True
1619 cert = "".join(cert).decode('base64')
1620 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001621
Doug Zongker412c02f2014-02-13 10:58:24 -08001622def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1623 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001624 """Generate a binary patch that creates the recovery image starting
1625 with the boot image. (Most of the space in these images is just the
1626 kernel, which is identical for the two, so the resulting patch
1627 should be efficient.) Add it to the output zip, along with a shell
1628 script that is run from init.rc on first boot to actually do the
1629 patching and install the new recovery image.
1630
1631 recovery_img and boot_img should be File objects for the
1632 corresponding images. info should be the dictionary returned by
1633 common.LoadInfoDict() on the input target_files.
1634 """
1635
Doug Zongker412c02f2014-02-13 10:58:24 -08001636 if info_dict is None:
1637 info_dict = OPTIONS.info_dict
1638
Tao Baof2cffbd2015-07-22 12:33:18 -07001639 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001640 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001641
Tao Baof2cffbd2015-07-22 12:33:18 -07001642 if full_recovery_image:
1643 output_sink("etc/recovery.img", recovery_img.data)
1644
1645 else:
1646 diff_program = ["imgdiff"]
1647 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1648 if os.path.exists(path):
1649 diff_program.append("-b")
1650 diff_program.append(path)
1651 bonus_args = "-b /system/etc/recovery-resource.dat"
1652 else:
1653 bonus_args = ""
1654
1655 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1656 _, _, patch = d.ComputePatch()
1657 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001658
Dan Albertebb19aa2015-03-27 19:11:53 -07001659 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001660 # The following GetTypeAndDevice()s need to use the path in the target
1661 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001662 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1663 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1664 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001665 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001666
Tao Baof2cffbd2015-07-22 12:33:18 -07001667 if full_recovery_image:
1668 sh = """#!/system/bin/sh
1669if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1670 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"
1671else
1672 log -t recovery "Recovery image already installed"
1673fi
1674""" % {'type': recovery_type,
1675 'device': recovery_device,
1676 'sha1': recovery_img.sha1,
1677 'size': recovery_img.size}
1678 else:
1679 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001680if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1681 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"
1682else
1683 log -t recovery "Recovery image already installed"
1684fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001685""" % {'boot_size': boot_img.size,
1686 'boot_sha1': boot_img.sha1,
1687 'recovery_size': recovery_img.size,
1688 'recovery_sha1': recovery_img.sha1,
1689 'boot_type': boot_type,
1690 'boot_device': boot_device,
1691 'recovery_type': recovery_type,
1692 'recovery_device': recovery_device,
1693 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001694
1695 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001696 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001697 # target-files expects it to be, and put it there.
1698 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001699 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001700 if system_root_image:
1701 init_rc_dir = os.path.join(input_dir, "ROOT")
1702 else:
1703 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001704 init_rc_files = os.listdir(init_rc_dir)
1705 for init_rc_file in init_rc_files:
1706 if (not init_rc_file.startswith('init.') or
1707 not init_rc_file.endswith('.rc')):
1708 continue
1709
1710 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001711 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001712 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001713 if m:
1714 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001715 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001716 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001717
1718 if found:
1719 break
1720
Tao Bao89fbb0f2017-01-10 10:47:58 -08001721 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001722
1723 output_sink(sh_location, sh)