blob: 3f3b0119f95cbcdc2c83841a65501871f6e1b6de [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
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
33
Tao Baof3282b42015-04-01 11:21:55 -070034from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080035
Doug Zongkereef39442009-04-02 12:14:19 -070036
Dan Albert8b72aef2015-03-23 19:13:21 -070037class Options(object):
38 def __init__(self):
39 platform_search_path = {
40 "linux2": "out/host/linux-x86",
41 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070042 }
Doug Zongker85448772014-09-09 14:59:20 -070043
Dan Albert8b72aef2015-03-23 19:13:21 -070044 self.search_path = platform_search_path.get(sys.platform, None)
45 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080046 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070047 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
49 self.java_args = "-Xmx2048m" # JVM Args
50 self.public_key_suffix = ".x509.pem"
51 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070054 self.boot_signer_args = []
55 self.verity_signer_path = None
56 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070057 self.verbose = False
58 self.tempfiles = []
59 self.device_specific = None
60 self.extras = {}
61 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070062 self.source_info_dict = None
63 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070064 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070065 # Stash size cannot exceed cache_size * threshold.
66 self.cache_size = None
67 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070068
69
70OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070071
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080072
73# Values for "certificate" in apkcerts that mean special things.
74SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
75
Tianjie Xu209db462016-05-24 17:34:52 -070076class ErrorCode(object):
77 """Define error_codes for failures that happen during the actual
78 update package installation.
79
80 Error codes 0-999 are reserved for failures before the package
81 installation (i.e. low battery, package verification failure).
82 Detailed code in 'bootable/recovery/error_code.h' """
83
84 SYSTEM_VERIFICATION_FAILURE = 1000
85 SYSTEM_UPDATE_FAILURE = 1001
86 SYSTEM_UNEXPECTED_CONTENTS = 1002
87 SYSTEM_NONZERO_CONTENTS = 1003
88 SYSTEM_RECOVER_FAILURE = 1004
89 VENDOR_VERIFICATION_FAILURE = 2000
90 VENDOR_UPDATE_FAILURE = 2001
91 VENDOR_UNEXPECTED_CONTENTS = 2002
92 VENDOR_NONZERO_CONTENTS = 2003
93 VENDOR_RECOVER_FAILURE = 2004
94 OEM_PROP_MISMATCH = 3000
95 FINGERPRINT_MISMATCH = 3001
96 THUMBPRINT_MISMATCH = 3002
97 OLDER_BUILD = 3003
98 DEVICE_MISMATCH = 3004
99 BAD_PATCH_FILE = 3005
100 INSUFFICIENT_CACHE_SPACE = 3006
101 TUNE_PARTITION_FAILURE = 3007
102 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800103
Dan Albert8b72aef2015-03-23 19:13:21 -0700104class ExternalError(RuntimeError):
105 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700106
107
108def Run(args, **kwargs):
109 """Create and return a subprocess.Popen object, printing the command
110 line on the terminal if -v was specified."""
111 if OPTIONS.verbose:
112 print " running: ", " ".join(args)
113 return subprocess.Popen(args, **kwargs)
114
115
Ying Wang7e6d4e42010-12-13 16:25:36 -0800116def CloseInheritedPipes():
117 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
118 before doing other work."""
119 if platform.system() != "Darwin":
120 return
121 for d in range(3, 1025):
122 try:
123 stat = os.fstat(d)
124 if stat is not None:
125 pipebit = stat[0] & 0x1000
126 if pipebit != 0:
127 os.close(d)
128 except OSError:
129 pass
130
131
Tao Bao2c15d9e2015-07-09 11:51:16 -0700132def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700133 """Read and parse the META/misc_info.txt key/value pairs from the
134 input target files and return a dict."""
135
Doug Zongkerc9253822014-02-04 12:17:58 -0800136 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700137 if isinstance(input_file, zipfile.ZipFile):
138 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800139 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700140 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800141 try:
142 with open(path) as f:
143 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700144 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800145 if e.errno == errno.ENOENT:
146 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700147 d = {}
148 try:
Michael Runge6e836112014-04-15 17:40:21 -0700149 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700150 except KeyError:
151 # ok if misc_info.txt doesn't exist
152 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700153
Doug Zongker37974732010-09-16 17:44:38 -0700154 # backwards compatibility: These values used to be in their own
155 # files. Look for them, in case we're processing an old
156 # target_files zip.
157
Doug Zongker37974732010-09-16 17:44:38 -0700158 if "recovery_api_version" not in d:
159 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700160 d["recovery_api_version"] = read_helper(
161 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700162 except KeyError:
163 raise ValueError("can't find recovery API version in input target-files")
164
165 if "tool_extensions" not in d:
166 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800167 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700168 except KeyError:
169 # ok if extensions don't exist
170 pass
171
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800172 if "fstab_version" not in d:
173 d["fstab_version"] = "1"
174
Tao Bao84e75682015-07-19 02:38:53 -0700175 # A few properties are stored as links to the files in the out/ directory.
176 # It works fine with the build system. However, they are no longer available
177 # when (re)generating from target_files zip. If input_dir is not None, we
178 # are doing repacking. Redirect those properties to the actual files in the
179 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700180 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400181 # We carry a copy of file_contexts.bin under META/. If not available,
182 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700183 # to build images than the one running on device, such as when enabling
184 # system_root_image. In that case, we must have the one for image
185 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700186 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
187 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700188 if d.get("system_root_image") == "true":
189 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700190 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700191 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700192 if not os.path.exists(fc_config):
193 fc_config = None
194
195 if fc_config:
196 d["selinux_fc"] = fc_config
197
Tao Bao84e75682015-07-19 02:38:53 -0700198 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
199 if d.get("system_root_image") == "true":
200 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
201 d["ramdisk_fs_config"] = os.path.join(
202 input_dir, "META", "root_filesystem_config.txt")
203
Tao Baof54216f2016-03-29 15:12:37 -0700204 # Redirect {system,vendor}_base_fs_file.
205 if "system_base_fs_file" in d:
206 basename = os.path.basename(d["system_base_fs_file"])
207 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700208 if os.path.exists(system_base_fs_file):
209 d["system_base_fs_file"] = system_base_fs_file
210 else:
211 print "Warning: failed to find system base fs file: %s" % (
212 system_base_fs_file,)
213 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700214
215 if "vendor_base_fs_file" in d:
216 basename = os.path.basename(d["vendor_base_fs_file"])
217 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700218 if os.path.exists(vendor_base_fs_file):
219 d["vendor_base_fs_file"] = vendor_base_fs_file
220 else:
221 print "Warning: failed to find vendor base fs file: %s" % (
222 vendor_base_fs_file,)
223 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700224
Doug Zongker37974732010-09-16 17:44:38 -0700225 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800226 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700227 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700228 if not line:
229 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700230 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 if not value:
232 continue
Doug Zongker37974732010-09-16 17:44:38 -0700233 if name == "blocksize":
234 d[name] = value
235 else:
236 d[name + "_size"] = value
237 except KeyError:
238 pass
239
240 def makeint(key):
241 if key in d:
242 d[key] = int(d[key], 0)
243
244 makeint("recovery_api_version")
245 makeint("blocksize")
246 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700247 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700248 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700249 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700250 makeint("recovery_size")
251 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800252 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700253
Tao Bao48550cc2015-11-19 17:05:46 -0800254 if d.get("no_recovery", False) == "true":
255 d["fstab"] = None
256 else:
257 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
258 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800259 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700260 return d
261
Doug Zongkerc9253822014-02-04 12:17:58 -0800262def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700263 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800264 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700265 except KeyError:
266 print "Warning: could not find SYSTEM/build.prop in %s" % zip
267 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700268 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700269
Michael Runge6e836112014-04-15 17:40:21 -0700270def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700271 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700272 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700273 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700274 if not line or line.startswith("#"):
275 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700276 if "=" in line:
277 name, value = line.split("=", 1)
278 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700279 return d
280
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700281def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700282 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700283 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700284 self.mount_point = mount_point
285 self.fs_type = fs_type
286 self.device = device
287 self.length = length
288 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700289 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700290
291 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800292 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700293 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800294 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700295 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700296
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800297 if fstab_version == 1:
298 d = {}
299 for line in data.split("\n"):
300 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700301 if not line or line.startswith("#"):
302 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800303 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700304 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800305 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800306 options = None
307 if len(pieces) >= 4:
308 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700309 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800310 if len(pieces) >= 5:
311 options = pieces[4]
312 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700313 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800314 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800315 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700316 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700317
Dan Albert8b72aef2015-03-23 19:13:21 -0700318 mount_point = pieces[0]
319 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800320 if options:
321 options = options.split(",")
322 for i in options:
323 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700324 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800325 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700326 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800327
Dan Albert8b72aef2015-03-23 19:13:21 -0700328 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
329 device=pieces[2], length=length,
330 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800331
332 elif fstab_version == 2:
333 d = {}
334 for line in data.split("\n"):
335 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700336 if not line or line.startswith("#"):
337 continue
Tao Bao548eb762015-06-10 12:32:41 -0700338 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800339 pieces = line.split()
340 if len(pieces) != 5:
341 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
342
343 # Ignore entries that are managed by vold
344 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700345 if "voldmanaged=" in options:
346 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800347
348 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700349 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800350 options = options.split(",")
351 for i in options:
352 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700353 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800354 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800355 # Ignore all unknown options in the unified fstab
356 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800357
Tao Bao548eb762015-06-10 12:32:41 -0700358 mount_flags = pieces[3]
359 # Honor the SELinux context if present.
360 context = None
361 for i in mount_flags.split(","):
362 if i.startswith("context="):
363 context = i
364
Dan Albert8b72aef2015-03-23 19:13:21 -0700365 mount_point = pieces[1]
366 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700367 device=pieces[0], length=length,
368 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800369
370 else:
371 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
372
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700373 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700374 # system. Other areas assume system is always at "/system" so point /system
375 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700376 if system_root_image:
377 assert not d.has_key("/system") and d.has_key("/")
378 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700379 return d
380
381
Doug Zongker37974732010-09-16 17:44:38 -0700382def DumpInfoDict(d):
383 for k, v in sorted(d.items()):
384 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700385
Dan Albert8b72aef2015-03-23 19:13:21 -0700386
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700387def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
388 has_ramdisk=False):
389 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700390
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700391 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
392 'sourcedir'), and turn them into a boot image. Return the image data, or
393 None if sourcedir does not appear to contains files for building the
394 requested image."""
395
396 def make_ramdisk():
397 ramdisk_img = tempfile.NamedTemporaryFile()
398
399 if os.access(fs_config_file, os.F_OK):
400 cmd = ["mkbootfs", "-f", fs_config_file,
401 os.path.join(sourcedir, "RAMDISK")]
402 else:
403 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
404 p1 = Run(cmd, stdout=subprocess.PIPE)
405 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
406
407 p2.wait()
408 p1.wait()
409 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
410 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
411
412 return ramdisk_img
413
414 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
415 return None
416
417 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700418 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700419
Doug Zongkerd5131602012-08-02 14:46:42 -0700420 if info_dict is None:
421 info_dict = OPTIONS.info_dict
422
Doug Zongkereef39442009-04-02 12:14:19 -0700423 img = tempfile.NamedTemporaryFile()
424
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700425 if has_ramdisk:
426 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700427
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800428 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
429 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
430
431 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700432
Benoit Fradina45a8682014-07-14 21:00:43 +0200433 fn = os.path.join(sourcedir, "second")
434 if os.access(fn, os.F_OK):
435 cmd.append("--second")
436 cmd.append(fn)
437
Doug Zongker171f1cd2009-06-15 22:36:37 -0700438 fn = os.path.join(sourcedir, "cmdline")
439 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700440 cmd.append("--cmdline")
441 cmd.append(open(fn).read().rstrip("\n"))
442
443 fn = os.path.join(sourcedir, "base")
444 if os.access(fn, os.F_OK):
445 cmd.append("--base")
446 cmd.append(open(fn).read().rstrip("\n"))
447
Ying Wang4de6b5b2010-08-25 14:29:34 -0700448 fn = os.path.join(sourcedir, "pagesize")
449 if os.access(fn, os.F_OK):
450 cmd.append("--pagesize")
451 cmd.append(open(fn).read().rstrip("\n"))
452
Doug Zongkerd5131602012-08-02 14:46:42 -0700453 args = info_dict.get("mkbootimg_args", None)
454 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700455 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700456
Sami Tolvanen3303d902016-03-15 16:49:30 +0000457 args = info_dict.get("mkbootimg_version_args", None)
458 if args and args.strip():
459 cmd.extend(shlex.split(args))
460
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700461 if has_ramdisk:
462 cmd.extend(["--ramdisk", ramdisk_img.name])
463
Tao Baod95e9fd2015-03-29 23:07:41 -0700464 img_unsigned = None
465 if info_dict.get("vboot", None):
466 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700467 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700468 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700469 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700470
471 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700472 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700473 assert p.returncode == 0, "mkbootimg of %s image failed" % (
474 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700475
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100476 if (info_dict.get("boot_signer", None) == "true" and
477 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700478 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700479 cmd = [OPTIONS.boot_signer_path]
480 cmd.extend(OPTIONS.boot_signer_args)
481 cmd.extend([path, img.name,
482 info_dict["verity_key"] + ".pk8",
483 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700484 p = Run(cmd, stdout=subprocess.PIPE)
485 p.communicate()
486 assert p.returncode == 0, "boot_signer of %s image failed" % path
487
Tao Baod95e9fd2015-03-29 23:07:41 -0700488 # Sign the image if vboot is non-empty.
489 elif info_dict.get("vboot", None):
490 path = "/" + os.path.basename(sourcedir).lower()
491 img_keyblock = tempfile.NamedTemporaryFile()
492 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
493 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700494 info_dict["vboot_key"] + ".vbprivk",
495 info_dict["vboot_subkey"] + ".vbprivk",
496 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700497 img.name]
498 p = Run(cmd, stdout=subprocess.PIPE)
499 p.communicate()
500 assert p.returncode == 0, "vboot_signer of %s image failed" % path
501
Tao Baof3282b42015-04-01 11:21:55 -0700502 # Clean up the temp files.
503 img_unsigned.close()
504 img_keyblock.close()
505
Doug Zongkereef39442009-04-02 12:14:19 -0700506 img.seek(os.SEEK_SET, 0)
507 data = img.read()
508
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700509 if has_ramdisk:
510 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700511 img.close()
512
513 return data
514
515
David Zeuthend995f4b2016-01-29 16:59:17 -0500516def _BuildBvbBootableImage(sourcedir, fs_config_file, system_img_path,
517 info_dict=None, has_ramdisk=False):
518 """Build a bootable image compatible with Brillo Verified Boot from the
519 specified sourcedir.
520
521 Take a kernel, cmdline, system image path, and optionally a ramdisk
522 directory from the input (in 'sourcedir'), and turn them into a boot
523 image. Return the image data, or None if sourcedir does not appear
524 to contains files for building the requested image.
525 """
526
527 def make_ramdisk():
528 ramdisk_img = tempfile.NamedTemporaryFile()
529
530 if os.access(fs_config_file, os.F_OK):
531 cmd = ["mkbootfs", "-f", fs_config_file,
532 os.path.join(sourcedir, "RAMDISK")]
533 else:
534 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
535 p1 = Run(cmd, stdout=subprocess.PIPE)
536 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
537
538 p2.wait()
539 p1.wait()
540 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
541 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
542
543 return ramdisk_img
544
545 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
546 return None
547
548 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
549 return None
550
551 if info_dict is None:
552 info_dict = OPTIONS.info_dict
553
554 img = tempfile.NamedTemporaryFile()
555
556 if has_ramdisk:
557 ramdisk_img = make_ramdisk()
558
559 # use BVBTOOL from environ, or "bvbtool" if empty or not set
560 bvbtool = os.getenv('BVBTOOL') or "bvbtool"
561
562 # First, create boot.img.
563 cmd = [bvbtool, "make_boot_image"]
564
565 fn = os.path.join(sourcedir, "cmdline")
566 if os.access(fn, os.F_OK):
567 cmd.append("--kernel_cmdline")
568 cmd.append(open(fn).read().rstrip("\n"))
569
570 cmd.extend(["--kernel", os.path.join(sourcedir, "kernel")])
571
572 if has_ramdisk:
573 cmd.extend(["--initrd", ramdisk_img.name])
574
575 cmd.extend(["--rootfs_with_hashes", system_img_path])
576
577 args = info_dict.get("board_bvb_make_boot_image_args", None)
578 if args and args.strip():
579 cmd.extend(shlex.split(args))
580
581 rollback_index = info_dict.get("board_bvb_rollback_index", None)
582 if rollback_index and rollback_index.strip():
583 cmd.extend(["--rollback_index", rollback_index.strip()])
584
585 cmd.extend(["--output", img.name])
586
587 p = Run(cmd, stdout=subprocess.PIPE)
588 p.communicate()
589 assert p.returncode == 0, "bvbtool make_boot_image of %s image failed" % (
590 os.path.basename(sourcedir),)
591
592 # Then, sign boot.img.
593 cmd = [bvbtool, "sign_boot_image", "--image", img.name]
594
595 algorithm = info_dict.get("board_bvb_algorithm", None)
596 key_path = info_dict.get("board_bvb_key_path", None)
597 if algorithm and algorithm.strip() and key_path and key_path.strip():
598 cmd.extend(["--algorithm", algorithm, "--key", key_path])
599 else:
600 cmd.extend(["--algorithm", "SHA256_RSA4096"])
Ethan Xia37b4a982016-06-27 17:19:01 +0800601 cmd.extend(["--key", "external/bvb/test/testkey_rsa4096.pem"])
David Zeuthend995f4b2016-01-29 16:59:17 -0500602
603 args = info_dict.get("board_bvb_sign_boot_image_args", None)
604 if args and args.strip():
605 cmd.extend(shlex.split(args))
606
607 p = Run(cmd, stdout=subprocess.PIPE)
608 p.communicate()
609 assert p.returncode == 0, "bvbtool sign_boot_image of %s image failed" % (
610 os.path.basename(sourcedir),)
611
612 img.seek(os.SEEK_SET, 0)
613 data = img.read()
614
615 if has_ramdisk:
616 ramdisk_img.close()
617 img.close()
618
619 return data
620
621
Doug Zongkerd5131602012-08-02 14:46:42 -0700622def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
David Zeuthend995f4b2016-01-29 16:59:17 -0500623 info_dict=None, system_img_path=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700624 """Return a File object with the desired bootable image.
625
626 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
627 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
628 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700629
Doug Zongker55d93282011-01-25 17:03:34 -0800630 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
631 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700632 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800633 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700634
635 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
636 if os.path.exists(prebuilt_path):
637 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
638 return File.FromLocalFile(name, prebuilt_path)
639
640 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700641
642 if info_dict is None:
643 info_dict = OPTIONS.info_dict
644
645 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800646 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
647 # for recovery.
648 has_ramdisk = (info_dict.get("system_root_image") != "true" or
649 prebuilt_name != "boot.img" or
650 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700651
Doug Zongker6f1d0312014-08-22 08:07:12 -0700652 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthend995f4b2016-01-29 16:59:17 -0500653 if info_dict.get("board_bvb_enable", None) == "true":
654 data = _BuildBvbBootableImage(os.path.join(unpack_dir, tree_subdir),
655 os.path.join(unpack_dir, fs_config),
656 system_img_path, info_dict, has_ramdisk)
657 else:
658 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
659 os.path.join(unpack_dir, fs_config),
660 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700661 if data:
662 return File(name, data)
663 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800664
Doug Zongkereef39442009-04-02 12:14:19 -0700665
Doug Zongker75f17362009-12-08 13:46:44 -0800666def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800667 """Unzip the given archive into a temporary directory and return the name.
668
669 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
670 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
671
672 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
673 main file), open for reading.
674 """
Doug Zongkereef39442009-04-02 12:14:19 -0700675
676 tmp = tempfile.mkdtemp(prefix="targetfiles-")
677 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800678
679 def unzip_to_dir(filename, dirname):
680 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
681 if pattern is not None:
682 cmd.append(pattern)
683 p = Run(cmd, stdout=subprocess.PIPE)
684 p.communicate()
685 if p.returncode != 0:
686 raise ExternalError("failed to unzip input target-files \"%s\"" %
687 (filename,))
688
689 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
690 if m:
691 unzip_to_dir(m.group(1), tmp)
692 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
693 filename = m.group(1)
694 else:
695 unzip_to_dir(filename, tmp)
696
697 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700698
699
700def GetKeyPasswords(keylist):
701 """Given a list of keys, prompt the user to enter passwords for
702 those which require them. Return a {key: password} dict. password
703 will be None if the key has no password."""
704
Doug Zongker8ce7c252009-05-22 13:34:54 -0700705 no_passwords = []
706 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700707 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700708 devnull = open("/dev/null", "w+b")
709 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800710 # We don't need a password for things that aren't really keys.
711 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700712 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700713 continue
714
T.R. Fullhart37e10522013-03-18 10:31:26 -0700715 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700716 "-inform", "DER", "-nocrypt"],
717 stdin=devnull.fileno(),
718 stdout=devnull.fileno(),
719 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700720 p.communicate()
721 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700722 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700723 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700724 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700725 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
726 "-inform", "DER", "-passin", "pass:"],
727 stdin=devnull.fileno(),
728 stdout=devnull.fileno(),
729 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700730 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700731 if p.returncode == 0:
732 # Encrypted key with empty string as password.
733 key_passwords[k] = ''
734 elif stderr.startswith('Error decrypting key'):
735 # Definitely encrypted key.
736 # It would have said "Error reading key" if it didn't parse correctly.
737 need_passwords.append(k)
738 else:
739 # Potentially, a type of key that openssl doesn't understand.
740 # We'll let the routines in signapk.jar handle it.
741 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700742 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700743
T.R. Fullhart37e10522013-03-18 10:31:26 -0700744 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700745 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700746 return key_passwords
747
748
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800749def GetMinSdkVersion(apk_name):
750 """Get the minSdkVersion delared in the APK. This can be both a decimal number
751 (API Level) or a codename.
752 """
753
754 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
755 output, err = p.communicate()
756 if err:
757 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
758 % (p.returncode,))
759
760 for line in output.split("\n"):
761 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
762 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
763 if m:
764 return m.group(1)
765 raise ExternalError("No minSdkVersion returned by aapt")
766
767
768def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
769 """Get the minSdkVersion declared in the APK as a number (API Level). If
770 minSdkVersion is set to a codename, it is translated to a number using the
771 provided map.
772 """
773
774 version = GetMinSdkVersion(apk_name)
775 try:
776 return int(version)
777 except ValueError:
778 # Not a decimal number. Codename?
779 if version in codename_to_api_level_map:
780 return codename_to_api_level_map[version]
781 else:
782 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
783 % (version, codename_to_api_level_map))
784
785
786def SignFile(input_name, output_name, key, password, min_api_level=None,
787 codename_to_api_level_map=dict(),
788 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700789 """Sign the input_name zip/jar/apk, producing output_name. Use the
790 given key and password (the latter may be None if the key does not
791 have a password.
792
Doug Zongker951495f2009-08-14 12:44:19 -0700793 If whole_file is true, use the "-w" option to SignApk to embed a
794 signature that covers the whole file in the archive comment of the
795 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800796
797 min_api_level is the API Level (int) of the oldest platform this file may end
798 up on. If not specified for an APK, the API Level is obtained by interpreting
799 the minSdkVersion attribute of the APK's AndroidManifest.xml.
800
801 codename_to_api_level_map is needed to translate the codename which may be
802 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700803 """
Doug Zongker951495f2009-08-14 12:44:19 -0700804
Alex Klyubin9667b182015-12-10 13:38:50 -0800805 java_library_path = os.path.join(
806 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
807
808 cmd = [OPTIONS.java_path, OPTIONS.java_args,
809 "-Djava.library.path=" + java_library_path,
810 "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700811 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
812 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700813 if whole_file:
814 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800815
816 min_sdk_version = min_api_level
817 if min_sdk_version is None:
818 if not whole_file:
819 min_sdk_version = GetMinSdkVersionInt(
820 input_name, codename_to_api_level_map)
821 if min_sdk_version is not None:
822 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
823
T.R. Fullhart37e10522013-03-18 10:31:26 -0700824 cmd.extend([key + OPTIONS.public_key_suffix,
825 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800826 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700827
828 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700829 if password is not None:
830 password += "\n"
831 p.communicate(password)
832 if p.returncode != 0:
833 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
834
Doug Zongkereef39442009-04-02 12:14:19 -0700835
Doug Zongker37974732010-09-16 17:44:38 -0700836def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700837 """Check the data string passed against the max size limit, if
838 any, for the given target. Raise exception if the data is too big.
839 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700840
Dan Albert8b72aef2015-03-23 19:13:21 -0700841 if target.endswith(".img"):
842 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700843 mount_point = "/" + target
844
Ying Wangf8824af2014-06-03 14:07:27 -0700845 fs_type = None
846 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700847 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700848 if mount_point == "/userdata":
849 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700850 p = info_dict["fstab"][mount_point]
851 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800852 device = p.device
853 if "/" in device:
854 device = device[device.rfind("/")+1:]
855 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700856 if not fs_type or not limit:
857 return
Doug Zongkereef39442009-04-02 12:14:19 -0700858
Andrew Boie0f9aec82012-02-14 09:32:52 -0800859 size = len(data)
860 pct = float(size) * 100.0 / limit
861 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
862 if pct >= 99.0:
863 raise ExternalError(msg)
864 elif pct >= 95.0:
865 print
866 print " WARNING: ", msg
867 print
868 elif OPTIONS.verbose:
869 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700870
871
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800872def ReadApkCerts(tf_zip):
873 """Given a target_files ZipFile, parse the META/apkcerts.txt file
874 and return a {package: cert} dict."""
875 certmap = {}
876 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
877 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700878 if not line:
879 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800880 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
881 r'private_key="(.*)"$', line)
882 if m:
883 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700884 public_key_suffix_len = len(OPTIONS.public_key_suffix)
885 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800886 if cert in SPECIAL_CERT_STRINGS and not privkey:
887 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700888 elif (cert.endswith(OPTIONS.public_key_suffix) and
889 privkey.endswith(OPTIONS.private_key_suffix) and
890 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
891 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800892 else:
893 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
894 return certmap
895
896
Doug Zongkereef39442009-04-02 12:14:19 -0700897COMMON_DOCSTRING = """
898 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700899 Prepend <dir>/bin to the list of places to search for binaries
900 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700901
Doug Zongker05d3dea2009-06-22 11:32:31 -0700902 -s (--device_specific) <file>
903 Path to the python module containing device-specific
904 releasetools code.
905
Doug Zongker8bec09e2009-11-30 15:37:14 -0800906 -x (--extra) <key=value>
907 Add a key/value pair to the 'extras' dict, which device-specific
908 extension code may look at.
909
Doug Zongkereef39442009-04-02 12:14:19 -0700910 -v (--verbose)
911 Show command lines being executed.
912
913 -h (--help)
914 Display this usage message and exit.
915"""
916
917def Usage(docstring):
918 print docstring.rstrip("\n")
919 print COMMON_DOCSTRING
920
921
922def ParseOptions(argv,
923 docstring,
924 extra_opts="", extra_long_opts=(),
925 extra_option_handler=None):
926 """Parse the options in argv and return any arguments that aren't
927 flags. docstring is the calling module's docstring, to be displayed
928 for errors and -h. extra_opts and extra_long_opts are for flags
929 defined by the caller, which are processed by passing them to
930 extra_option_handler."""
931
932 try:
933 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800934 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800935 ["help", "verbose", "path=", "signapk_path=",
936 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700937 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700938 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
939 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800940 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700941 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700942 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700943 Usage(docstring)
944 print "**", str(err), "**"
945 sys.exit(2)
946
Doug Zongkereef39442009-04-02 12:14:19 -0700947 for o, a in opts:
948 if o in ("-h", "--help"):
949 Usage(docstring)
950 sys.exit()
951 elif o in ("-v", "--verbose"):
952 OPTIONS.verbose = True
953 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700954 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700955 elif o in ("--signapk_path",):
956 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800957 elif o in ("--signapk_shared_library_path",):
958 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700959 elif o in ("--extra_signapk_args",):
960 OPTIONS.extra_signapk_args = shlex.split(a)
961 elif o in ("--java_path",):
962 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700963 elif o in ("--java_args",):
964 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700965 elif o in ("--public_key_suffix",):
966 OPTIONS.public_key_suffix = a
967 elif o in ("--private_key_suffix",):
968 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800969 elif o in ("--boot_signer_path",):
970 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700971 elif o in ("--boot_signer_args",):
972 OPTIONS.boot_signer_args = shlex.split(a)
973 elif o in ("--verity_signer_path",):
974 OPTIONS.verity_signer_path = a
975 elif o in ("--verity_signer_args",):
976 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700977 elif o in ("-s", "--device_specific"):
978 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800979 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800980 key, value = a.split("=", 1)
981 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700982 else:
983 if extra_option_handler is None or not extra_option_handler(o, a):
984 assert False, "unknown option \"%s\"" % (o,)
985
Doug Zongker85448772014-09-09 14:59:20 -0700986 if OPTIONS.search_path:
987 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
988 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700989
990 return args
991
992
Doug Zongkerfc44a512014-08-26 13:10:25 -0700993def MakeTempFile(prefix=None, suffix=None):
994 """Make a temp file and add it to the list of things to be deleted
995 when Cleanup() is called. Return the filename."""
996 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
997 os.close(fd)
998 OPTIONS.tempfiles.append(fn)
999 return fn
1000
1001
Doug Zongkereef39442009-04-02 12:14:19 -07001002def Cleanup():
1003 for i in OPTIONS.tempfiles:
1004 if os.path.isdir(i):
1005 shutil.rmtree(i)
1006 else:
1007 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001008
1009
1010class PasswordManager(object):
1011 def __init__(self):
1012 self.editor = os.getenv("EDITOR", None)
1013 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
1014
1015 def GetPasswords(self, items):
1016 """Get passwords corresponding to each string in 'items',
1017 returning a dict. (The dict may have keys in addition to the
1018 values in 'items'.)
1019
1020 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1021 user edit that file to add more needed passwords. If no editor is
1022 available, or $ANDROID_PW_FILE isn't define, prompts the user
1023 interactively in the ordinary way.
1024 """
1025
1026 current = self.ReadFile()
1027
1028 first = True
1029 while True:
1030 missing = []
1031 for i in items:
1032 if i not in current or not current[i]:
1033 missing.append(i)
1034 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001035 if not missing:
1036 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001037
1038 for i in missing:
1039 current[i] = ""
1040
1041 if not first:
1042 print "key file %s still missing some passwords." % (self.pwfile,)
1043 answer = raw_input("try to edit again? [y]> ").strip()
1044 if answer and answer[0] not in 'yY':
1045 raise RuntimeError("key passwords unavailable")
1046 first = False
1047
1048 current = self.UpdateAndReadFile(current)
1049
Dan Albert8b72aef2015-03-23 19:13:21 -07001050 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001051 """Prompt the user to enter a value (password) for each key in
1052 'current' whose value is fales. Returns a new dict with all the
1053 values.
1054 """
1055 result = {}
1056 for k, v in sorted(current.iteritems()):
1057 if v:
1058 result[k] = v
1059 else:
1060 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001061 result[k] = getpass.getpass(
1062 "Enter password for %s key> " % k).strip()
1063 if result[k]:
1064 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001065 return result
1066
1067 def UpdateAndReadFile(self, current):
1068 if not self.editor or not self.pwfile:
1069 return self.PromptResult(current)
1070
1071 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001072 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001073 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1074 f.write("# (Additional spaces are harmless.)\n\n")
1075
1076 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001077 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1078 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001079 f.write("[[[ %s ]]] %s\n" % (v, k))
1080 if not v and first_line is None:
1081 # position cursor on first line with no password.
1082 first_line = i + 4
1083 f.close()
1084
1085 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1086 _, _ = p.communicate()
1087
1088 return self.ReadFile()
1089
1090 def ReadFile(self):
1091 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001092 if self.pwfile is None:
1093 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001094 try:
1095 f = open(self.pwfile, "r")
1096 for line in f:
1097 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001098 if not line or line[0] == '#':
1099 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001100 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1101 if not m:
1102 print "failed to parse password file: ", line
1103 else:
1104 result[m.group(2)] = m.group(1)
1105 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001106 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001107 if e.errno != errno.ENOENT:
1108 print "error reading password file: ", str(e)
1109 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001110
1111
Dan Albert8e0178d2015-01-27 15:53:15 -08001112def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1113 compress_type=None):
1114 import datetime
1115
1116 # http://b/18015246
1117 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1118 # for files larger than 2GiB. We can work around this by adjusting their
1119 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1120 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1121 # it isn't clear to me exactly what circumstances cause this).
1122 # `zipfile.write()` must be used directly to work around this.
1123 #
1124 # This mess can be avoided if we port to python3.
1125 saved_zip64_limit = zipfile.ZIP64_LIMIT
1126 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1127
1128 if compress_type is None:
1129 compress_type = zip_file.compression
1130 if arcname is None:
1131 arcname = filename
1132
1133 saved_stat = os.stat(filename)
1134
1135 try:
1136 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1137 # file to be zipped and reset it when we're done.
1138 os.chmod(filename, perms)
1139
1140 # Use a fixed timestamp so the output is repeatable.
1141 epoch = datetime.datetime.fromtimestamp(0)
1142 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1143 os.utime(filename, (timestamp, timestamp))
1144
1145 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1146 finally:
1147 os.chmod(filename, saved_stat.st_mode)
1148 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1149 zipfile.ZIP64_LIMIT = saved_zip64_limit
1150
1151
Tao Bao58c1b962015-05-20 09:32:18 -07001152def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001153 compress_type=None):
1154 """Wrap zipfile.writestr() function to work around the zip64 limit.
1155
1156 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1157 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1158 when calling crc32(bytes).
1159
1160 But it still works fine to write a shorter string into a large zip file.
1161 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1162 when we know the string won't be too long.
1163 """
1164
1165 saved_zip64_limit = zipfile.ZIP64_LIMIT
1166 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1167
1168 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1169 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001170 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001171 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001172 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001173 else:
Tao Baof3282b42015-04-01 11:21:55 -07001174 zinfo = zinfo_or_arcname
1175
1176 # If compress_type is given, it overrides the value in zinfo.
1177 if compress_type is not None:
1178 zinfo.compress_type = compress_type
1179
Tao Bao58c1b962015-05-20 09:32:18 -07001180 # If perms is given, it has a priority.
1181 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001182 # If perms doesn't set the file type, mark it as a regular file.
1183 if perms & 0o770000 == 0:
1184 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001185 zinfo.external_attr = perms << 16
1186
Tao Baof3282b42015-04-01 11:21:55 -07001187 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001188 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1189
Dan Albert8b72aef2015-03-23 19:13:21 -07001190 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001191 zipfile.ZIP64_LIMIT = saved_zip64_limit
1192
1193
1194def ZipClose(zip_file):
1195 # http://b/18015246
1196 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1197 # central directory.
1198 saved_zip64_limit = zipfile.ZIP64_LIMIT
1199 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1200
1201 zip_file.close()
1202
1203 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001204
1205
1206class DeviceSpecificParams(object):
1207 module = None
1208 def __init__(self, **kwargs):
1209 """Keyword arguments to the constructor become attributes of this
1210 object, which is passed to all functions in the device-specific
1211 module."""
1212 for k, v in kwargs.iteritems():
1213 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001214 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001215
1216 if self.module is None:
1217 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001218 if not path:
1219 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001220 try:
1221 if os.path.isdir(path):
1222 info = imp.find_module("releasetools", [path])
1223 else:
1224 d, f = os.path.split(path)
1225 b, x = os.path.splitext(f)
1226 if x == ".py":
1227 f = b
1228 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001229 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001230 self.module = imp.load_module("device_specific", *info)
1231 except ImportError:
1232 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001233
1234 def _DoCall(self, function_name, *args, **kwargs):
1235 """Call the named function in the device-specific module, passing
1236 the given args and kwargs. The first argument to the call will be
1237 the DeviceSpecific object itself. If there is no module, or the
1238 module does not define the function, return the value of the
1239 'default' kwarg (which itself defaults to None)."""
1240 if self.module is None or not hasattr(self.module, function_name):
1241 return kwargs.get("default", None)
1242 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1243
1244 def FullOTA_Assertions(self):
1245 """Called after emitting the block of assertions at the top of a
1246 full OTA package. Implementations can add whatever additional
1247 assertions they like."""
1248 return self._DoCall("FullOTA_Assertions")
1249
Doug Zongkere5ff5902012-01-17 10:55:37 -08001250 def FullOTA_InstallBegin(self):
1251 """Called at the start of full OTA installation."""
1252 return self._DoCall("FullOTA_InstallBegin")
1253
Doug Zongker05d3dea2009-06-22 11:32:31 -07001254 def FullOTA_InstallEnd(self):
1255 """Called at the end of full OTA installation; typically this is
1256 used to install the image for the device's baseband processor."""
1257 return self._DoCall("FullOTA_InstallEnd")
1258
1259 def IncrementalOTA_Assertions(self):
1260 """Called after emitting the block of assertions at the top of an
1261 incremental OTA package. Implementations can add whatever
1262 additional assertions they like."""
1263 return self._DoCall("IncrementalOTA_Assertions")
1264
Doug Zongkere5ff5902012-01-17 10:55:37 -08001265 def IncrementalOTA_VerifyBegin(self):
1266 """Called at the start of the verification phase of incremental
1267 OTA installation; additional checks can be placed here to abort
1268 the script before any changes are made."""
1269 return self._DoCall("IncrementalOTA_VerifyBegin")
1270
Doug Zongker05d3dea2009-06-22 11:32:31 -07001271 def IncrementalOTA_VerifyEnd(self):
1272 """Called at the end of the verification phase of incremental OTA
1273 installation; additional checks can be placed here to abort the
1274 script before any changes are made."""
1275 return self._DoCall("IncrementalOTA_VerifyEnd")
1276
Doug Zongkere5ff5902012-01-17 10:55:37 -08001277 def IncrementalOTA_InstallBegin(self):
1278 """Called at the start of incremental OTA installation (after
1279 verification is complete)."""
1280 return self._DoCall("IncrementalOTA_InstallBegin")
1281
Doug Zongker05d3dea2009-06-22 11:32:31 -07001282 def IncrementalOTA_InstallEnd(self):
1283 """Called at the end of incremental OTA installation; typically
1284 this is used to install the image for the device's baseband
1285 processor."""
1286 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001287
Tao Bao9bc6bb22015-11-09 16:58:28 -08001288 def VerifyOTA_Assertions(self):
1289 return self._DoCall("VerifyOTA_Assertions")
1290
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001291class File(object):
1292 def __init__(self, name, data):
1293 self.name = name
1294 self.data = data
1295 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001296 self.sha1 = sha1(data).hexdigest()
1297
1298 @classmethod
1299 def FromLocalFile(cls, name, diskname):
1300 f = open(diskname, "rb")
1301 data = f.read()
1302 f.close()
1303 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001304
1305 def WriteToTemp(self):
1306 t = tempfile.NamedTemporaryFile()
1307 t.write(self.data)
1308 t.flush()
1309 return t
1310
Geremy Condra36bd3652014-02-06 19:45:10 -08001311 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001312 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001313
1314DIFF_PROGRAM_BY_EXT = {
1315 ".gz" : "imgdiff",
1316 ".zip" : ["imgdiff", "-z"],
1317 ".jar" : ["imgdiff", "-z"],
1318 ".apk" : ["imgdiff", "-z"],
1319 ".img" : "imgdiff",
1320 }
1321
1322class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001323 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001324 self.tf = tf
1325 self.sf = sf
1326 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001327 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001328
1329 def ComputePatch(self):
1330 """Compute the patch (as a string of data) needed to turn sf into
1331 tf. Returns the same tuple as GetPatch()."""
1332
1333 tf = self.tf
1334 sf = self.sf
1335
Doug Zongker24cd2802012-08-14 16:36:15 -07001336 if self.diff_program:
1337 diff_program = self.diff_program
1338 else:
1339 ext = os.path.splitext(tf.name)[1]
1340 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001341
1342 ttemp = tf.WriteToTemp()
1343 stemp = sf.WriteToTemp()
1344
1345 ext = os.path.splitext(tf.name)[1]
1346
1347 try:
1348 ptemp = tempfile.NamedTemporaryFile()
1349 if isinstance(diff_program, list):
1350 cmd = copy.copy(diff_program)
1351 else:
1352 cmd = [diff_program]
1353 cmd.append(stemp.name)
1354 cmd.append(ttemp.name)
1355 cmd.append(ptemp.name)
1356 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001357 err = []
1358 def run():
1359 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001360 if e:
1361 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001362 th = threading.Thread(target=run)
1363 th.start()
1364 th.join(timeout=300) # 5 mins
1365 if th.is_alive():
1366 print "WARNING: diff command timed out"
1367 p.terminate()
1368 th.join(5)
1369 if th.is_alive():
1370 p.kill()
1371 th.join()
1372
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001373 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001374 print "WARNING: failure running %s:\n%s\n" % (
1375 diff_program, "".join(err))
1376 self.patch = None
1377 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001378 diff = ptemp.read()
1379 finally:
1380 ptemp.close()
1381 stemp.close()
1382 ttemp.close()
1383
1384 self.patch = diff
1385 return self.tf, self.sf, self.patch
1386
1387
1388 def GetPatch(self):
1389 """Return a tuple (target_file, source_file, patch_data).
1390 patch_data may be None if ComputePatch hasn't been called, or if
1391 computing the patch failed."""
1392 return self.tf, self.sf, self.patch
1393
1394
1395def ComputeDifferences(diffs):
1396 """Call ComputePatch on all the Difference objects in 'diffs'."""
1397 print len(diffs), "diffs to compute"
1398
1399 # Do the largest files first, to try and reduce the long-pole effect.
1400 by_size = [(i.tf.size, i) for i in diffs]
1401 by_size.sort(reverse=True)
1402 by_size = [i[1] for i in by_size]
1403
1404 lock = threading.Lock()
1405 diff_iter = iter(by_size) # accessed under lock
1406
1407 def worker():
1408 try:
1409 lock.acquire()
1410 for d in diff_iter:
1411 lock.release()
1412 start = time.time()
1413 d.ComputePatch()
1414 dur = time.time() - start
1415 lock.acquire()
1416
1417 tf, sf, patch = d.GetPatch()
1418 if sf.name == tf.name:
1419 name = tf.name
1420 else:
1421 name = "%s (%s)" % (tf.name, sf.name)
1422 if patch is None:
1423 print "patching failed! %s" % (name,)
1424 else:
1425 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1426 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1427 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001428 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001429 print e
1430 raise
1431
1432 # start worker threads; wait for them all to finish.
1433 threads = [threading.Thread(target=worker)
1434 for i in range(OPTIONS.worker_threads)]
1435 for th in threads:
1436 th.start()
1437 while threads:
1438 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001439
1440
Dan Albert8b72aef2015-03-23 19:13:21 -07001441class BlockDifference(object):
1442 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001443 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001444 self.tgt = tgt
1445 self.src = src
1446 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001447 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001448 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001449
Tao Baodd2a5892015-03-12 12:32:37 -07001450 if version is None:
1451 version = 1
1452 if OPTIONS.info_dict:
1453 version = max(
1454 int(i) for i in
1455 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1456 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001457
1458 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001459 version=self.version,
1460 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001461 tmpdir = tempfile.mkdtemp()
1462 OPTIONS.tempfiles.append(tmpdir)
1463 self.path = os.path.join(tmpdir, partition)
1464 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001465 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001466 self.touched_src_ranges = b.touched_src_ranges
1467 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001468
Tao Baoaac4ad52015-10-16 15:26:34 -07001469 if src is None:
1470 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1471 else:
1472 _, self.device = GetTypeAndDevice("/" + partition,
1473 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001474
Tao Baod8d14be2016-02-04 14:26:02 -08001475 @property
1476 def required_cache(self):
1477 return self._required_cache
1478
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001479 def WriteScript(self, script, output_zip, progress=None):
1480 if not self.src:
1481 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001482 script.Print("Patching %s image unconditionally..." % (self.partition,))
1483 else:
1484 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001485
Dan Albert8b72aef2015-03-23 19:13:21 -07001486 if progress:
1487 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001488 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001489 if OPTIONS.verify:
1490 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001491
Tao Bao9bc6bb22015-11-09 16:58:28 -08001492 def WriteStrictVerifyScript(self, script):
1493 """Verify all the blocks in the care_map, including clobbered blocks.
1494
1495 This differs from the WriteVerifyScript() function: a) it prints different
1496 error messages; b) it doesn't allow half-way updated images to pass the
1497 verification."""
1498
1499 partition = self.partition
1500 script.Print("Verifying %s..." % (partition,))
1501 ranges = self.tgt.care_map
1502 ranges_str = ranges.to_string_raw()
1503 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1504 'ui_print(" Verified.") || '
1505 'ui_print("\\"%s\\" has unexpected contents.");' % (
1506 self.device, ranges_str,
1507 self.tgt.TotalSha1(include_clobbered_blocks=True),
1508 self.device))
1509 script.AppendExtra("")
1510
Tao Baod522bdc2016-04-12 15:53:16 -07001511 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001512 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001513
1514 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001515 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001516 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001517
1518 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001519 else:
Tao Baod522bdc2016-04-12 15:53:16 -07001520 if touched_blocks_only and self.version >= 3:
1521 ranges = self.touched_src_ranges
1522 expected_sha1 = self.touched_src_sha1
1523 else:
1524 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1525 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001526
1527 # No blocks to be checked, skipping.
1528 if not ranges:
1529 return
1530
Tao Bao5ece99d2015-05-12 11:42:31 -07001531 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001532 if self.version >= 4:
1533 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1534 'block_image_verify("%s", '
1535 'package_extract_file("%s.transfer.list"), '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001536 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baod522bdc2016-04-12 15:53:16 -07001537 self.device, ranges_str, expected_sha1,
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001538 self.device, partition, partition, partition))
1539 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001540 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1541 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001542 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001543 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baod522bdc2016-04-12 15:53:16 -07001544 self.device, ranges_str, expected_sha1,
Sami Tolvanene09d0962015-04-24 11:54:01 +01001545 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001546 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001547 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001548 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001549 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001550 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001551
Tianjie Xufc3422a2015-12-15 11:53:59 -08001552 if self.version >= 4:
1553
1554 # Bug: 21124327
1555 # When generating incrementals for the system and vendor partitions in
1556 # version 4 or newer, explicitly check the first block (which contains
1557 # the superblock) of the partition to see if it's what we expect. If
1558 # this check fails, give an explicit log message about the partition
1559 # having been remounted R/W (the most likely explanation).
1560 if self.check_first_block:
1561 script.AppendExtra('check_first_block("%s");' % (self.device,))
1562
1563 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001564 if partition == "system":
1565 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1566 else:
1567 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001568 script.AppendExtra((
1569 'ifelse (block_image_recover("{device}", "{ranges}") && '
1570 'block_image_verify("{device}", '
1571 'package_extract_file("{partition}.transfer.list"), '
1572 '"{partition}.new.dat", "{partition}.patch.dat"), '
1573 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001574 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001575 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001576 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001577
Tao Baodd2a5892015-03-12 12:32:37 -07001578 # Abort the OTA update. Note that the incremental OTA cannot be applied
1579 # even if it may match the checksum of the target partition.
1580 # a) If version < 3, operations like move and erase will make changes
1581 # unconditionally and damage the partition.
1582 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001583 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001584 if partition == "system":
1585 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1586 else:
1587 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1588 script.AppendExtra((
1589 'abort("E%d: %s partition has unexpected contents");\n'
1590 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001591
Tao Bao5fcaaef2015-06-01 13:40:49 -07001592 def _WritePostInstallVerifyScript(self, script):
1593 partition = self.partition
1594 script.Print('Verifying the updated %s image...' % (partition,))
1595 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1596 ranges = self.tgt.care_map
1597 ranges_str = ranges.to_string_raw()
1598 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1599 self.device, ranges_str,
1600 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001601
1602 # Bug: 20881595
1603 # Verify that extended blocks are really zeroed out.
1604 if self.tgt.extended:
1605 ranges_str = self.tgt.extended.to_string_raw()
1606 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1607 self.device, ranges_str,
1608 self._HashZeroBlocks(self.tgt.extended.size())))
1609 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001610 if partition == "system":
1611 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1612 else:
1613 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001614 script.AppendExtra(
1615 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001616 ' abort("E%d: %s partition has unexpected non-zero contents after '
1617 'OTA update");\n'
1618 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001619 else:
1620 script.Print('Verified the updated %s image.' % (partition,))
1621
Tianjie Xu209db462016-05-24 17:34:52 -07001622 if partition == "system":
1623 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1624 else:
1625 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1626
Tao Bao5fcaaef2015-06-01 13:40:49 -07001627 script.AppendExtra(
1628 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001629 ' abort("E%d: %s partition has unexpected contents after OTA '
1630 'update");\n'
1631 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001632
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001633 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001634 ZipWrite(output_zip,
1635 '{}.transfer.list'.format(self.path),
1636 '{}.transfer.list'.format(self.partition))
1637 ZipWrite(output_zip,
1638 '{}.new.dat'.format(self.path),
1639 '{}.new.dat'.format(self.partition))
1640 ZipWrite(output_zip,
1641 '{}.patch.dat'.format(self.path),
1642 '{}.patch.dat'.format(self.partition),
1643 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001644
Tianjie Xu209db462016-05-24 17:34:52 -07001645 if self.partition == "system":
1646 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1647 else:
1648 code = ErrorCode.VENDOR_UPDATE_FAILURE
1649
Dan Albert8e0178d2015-01-27 15:53:15 -08001650 call = ('block_image_update("{device}", '
1651 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub2deb222016-03-25 15:01:33 -07001652 '"{partition}.new.dat", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001653 ' abort("E{code}: Failed to update {partition} image.");'.format(
1654 device=self.device, partition=self.partition, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001655 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001656
Dan Albert8b72aef2015-03-23 19:13:21 -07001657 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001658 data = source.ReadRangeSet(ranges)
1659 ctx = sha1()
1660
1661 for p in data:
1662 ctx.update(p)
1663
1664 return ctx.hexdigest()
1665
Tao Baoe9b61912015-07-09 17:37:49 -07001666 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1667 """Return the hash value for all zero blocks."""
1668 zero_block = '\x00' * 4096
1669 ctx = sha1()
1670 for _ in range(num_blocks):
1671 ctx.update(zero_block)
1672
1673 return ctx.hexdigest()
1674
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001675
1676DataImage = blockimgdiff.DataImage
1677
Doug Zongker96a57e72010-09-26 14:57:41 -07001678# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001679PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001680 "ext4": "EMMC",
1681 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001682 "f2fs": "EMMC",
1683 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001684}
Doug Zongker96a57e72010-09-26 14:57:41 -07001685
1686def GetTypeAndDevice(mount_point, info):
1687 fstab = info["fstab"]
1688 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001689 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1690 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001691 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001692 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001693
1694
1695def ParseCertificate(data):
1696 """Parse a PEM-format certificate."""
1697 cert = []
1698 save = False
1699 for line in data.split("\n"):
1700 if "--END CERTIFICATE--" in line:
1701 break
1702 if save:
1703 cert.append(line)
1704 if "--BEGIN CERTIFICATE--" in line:
1705 save = True
1706 cert = "".join(cert).decode('base64')
1707 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001708
Doug Zongker412c02f2014-02-13 10:58:24 -08001709def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1710 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001711 """Generate a binary patch that creates the recovery image starting
1712 with the boot image. (Most of the space in these images is just the
1713 kernel, which is identical for the two, so the resulting patch
1714 should be efficient.) Add it to the output zip, along with a shell
1715 script that is run from init.rc on first boot to actually do the
1716 patching and install the new recovery image.
1717
1718 recovery_img and boot_img should be File objects for the
1719 corresponding images. info should be the dictionary returned by
1720 common.LoadInfoDict() on the input target_files.
1721 """
1722
Doug Zongker412c02f2014-02-13 10:58:24 -08001723 if info_dict is None:
1724 info_dict = OPTIONS.info_dict
1725
Tao Baof2cffbd2015-07-22 12:33:18 -07001726 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001727 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001728
Tao Baof2cffbd2015-07-22 12:33:18 -07001729 if full_recovery_image:
1730 output_sink("etc/recovery.img", recovery_img.data)
1731
1732 else:
1733 diff_program = ["imgdiff"]
1734 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1735 if os.path.exists(path):
1736 diff_program.append("-b")
1737 diff_program.append(path)
1738 bonus_args = "-b /system/etc/recovery-resource.dat"
1739 else:
1740 bonus_args = ""
1741
1742 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1743 _, _, patch = d.ComputePatch()
1744 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001745
Dan Albertebb19aa2015-03-27 19:11:53 -07001746 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001747 # The following GetTypeAndDevice()s need to use the path in the target
1748 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001749 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1750 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1751 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001752 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001753
Tao Baof2cffbd2015-07-22 12:33:18 -07001754 if full_recovery_image:
1755 sh = """#!/system/bin/sh
1756if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1757 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"
1758else
1759 log -t recovery "Recovery image already installed"
1760fi
1761""" % {'type': recovery_type,
1762 'device': recovery_device,
1763 'sha1': recovery_img.sha1,
1764 'size': recovery_img.size}
1765 else:
1766 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001767if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1768 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"
1769else
1770 log -t recovery "Recovery image already installed"
1771fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001772""" % {'boot_size': boot_img.size,
1773 'boot_sha1': boot_img.sha1,
1774 'recovery_size': recovery_img.size,
1775 'recovery_sha1': recovery_img.sha1,
1776 'boot_type': boot_type,
1777 'boot_device': boot_device,
1778 'recovery_type': recovery_type,
1779 'recovery_device': recovery_device,
1780 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001781
1782 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001783 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001784 # target-files expects it to be, and put it there.
1785 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001786 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001787 if system_root_image:
1788 init_rc_dir = os.path.join(input_dir, "ROOT")
1789 else:
1790 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001791 init_rc_files = os.listdir(init_rc_dir)
1792 for init_rc_file in init_rc_files:
1793 if (not init_rc_file.startswith('init.') or
1794 not init_rc_file.endswith('.rc')):
1795 continue
1796
1797 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001798 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001799 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001800 if m:
1801 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001802 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001803 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001804
1805 if found:
1806 break
1807
1808 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001809
1810 output_sink(sh_location, sh)