blob: c022d2441cda3adece82e67cc604cacc888a9fcb [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
Tao Bao39451582017-05-04 11:10:47 -0700110def Run(args, verbose=None, **kwargs):
111 """Create and return a subprocess.Popen object.
112
113 Caller can specify if the command line should be printed. The global
114 OPTIONS.verbose will be used if not specified.
115 """
116 if verbose is None:
117 verbose = OPTIONS.verbose
118 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800119 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700120 return subprocess.Popen(args, **kwargs)
121
122
Ying Wang7e6d4e42010-12-13 16:25:36 -0800123def CloseInheritedPipes():
124 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
125 before doing other work."""
126 if platform.system() != "Darwin":
127 return
128 for d in range(3, 1025):
129 try:
130 stat = os.fstat(d)
131 if stat is not None:
132 pipebit = stat[0] & 0x1000
133 if pipebit != 0:
134 os.close(d)
135 except OSError:
136 pass
137
138
Tao Bao2c15d9e2015-07-09 11:51:16 -0700139def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700140 """Read and parse the META/misc_info.txt key/value pairs from the
141 input target files and return a dict."""
142
Doug Zongkerc9253822014-02-04 12:17:58 -0800143 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700144 if isinstance(input_file, zipfile.ZipFile):
145 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800146 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700147 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800148 try:
149 with open(path) as f:
150 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700151 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800152 if e.errno == errno.ENOENT:
153 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800154
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700155 try:
Michael Runge6e836112014-04-15 17:40:21 -0700156 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700157 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800158 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700159
Tao Bao6cd54732017-02-27 15:12:05 -0800160 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800161 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800162
Tao Bao84e75682015-07-19 02:38:53 -0700163 # A few properties are stored as links to the files in the out/ directory.
164 # It works fine with the build system. However, they are no longer available
165 # when (re)generating from target_files zip. If input_dir is not None, we
166 # are doing repacking. Redirect those properties to the actual files in the
167 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700168 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400169 # We carry a copy of file_contexts.bin under META/. If not available,
170 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700171 # to build images than the one running on device, such as when enabling
172 # system_root_image. In that case, we must have the one for image
173 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700174 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
175 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700176 if d.get("system_root_image") == "true":
177 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700178 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700179 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700180 if not os.path.exists(fc_config):
181 fc_config = None
182
183 if fc_config:
184 d["selinux_fc"] = fc_config
185
Tao Bao84e75682015-07-19 02:38:53 -0700186 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
187 if d.get("system_root_image") == "true":
188 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
189 d["ramdisk_fs_config"] = os.path.join(
190 input_dir, "META", "root_filesystem_config.txt")
191
Tao Baof54216f2016-03-29 15:12:37 -0700192 # Redirect {system,vendor}_base_fs_file.
193 if "system_base_fs_file" in d:
194 basename = os.path.basename(d["system_base_fs_file"])
195 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700196 if os.path.exists(system_base_fs_file):
197 d["system_base_fs_file"] = system_base_fs_file
198 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800199 print("Warning: failed to find system base fs file: %s" % (
200 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700201 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700202
203 if "vendor_base_fs_file" in d:
204 basename = os.path.basename(d["vendor_base_fs_file"])
205 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700206 if os.path.exists(vendor_base_fs_file):
207 d["vendor_base_fs_file"] = vendor_base_fs_file
208 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800209 print("Warning: failed to find vendor base fs file: %s" % (
210 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700211 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700212
Doug Zongker37974732010-09-16 17:44:38 -0700213 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800214 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700215 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700216 if not line:
217 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700218 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700219 if not value:
220 continue
Doug Zongker37974732010-09-16 17:44:38 -0700221 if name == "blocksize":
222 d[name] = value
223 else:
224 d[name + "_size"] = value
225 except KeyError:
226 pass
227
228 def makeint(key):
229 if key in d:
230 d[key] = int(d[key], 0)
231
232 makeint("recovery_api_version")
233 makeint("blocksize")
234 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700235 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700236 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700237 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700238 makeint("recovery_size")
239 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800240 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700241
Tianjie Xucfa86222016-03-07 16:31:19 -0800242 system_root_image = d.get("system_root_image", None) == "true"
243 if d.get("no_recovery", None) != "true":
244 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800245 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800246 recovery_fstab_path, system_root_image)
247 elif d.get("recovery_as_boot", None) == "true":
248 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
249 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
250 recovery_fstab_path, system_root_image)
251 else:
252 d["fstab"] = None
253
Doug Zongkerc9253822014-02-04 12:17:58 -0800254 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700255 return d
256
Tao Baod1de6f32017-03-01 16:38:48 -0800257
Doug Zongkerc9253822014-02-04 12:17:58 -0800258def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700259 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800260 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700261 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800262 print("Warning: could not find SYSTEM/build.prop in %s" % (zip,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700263 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700264 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700265
Tao Baod1de6f32017-03-01 16:38:48 -0800266
Michael Runge6e836112014-04-15 17:40:21 -0700267def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700268 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700269 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700270 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700271 if not line or line.startswith("#"):
272 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700273 if "=" in line:
274 name, value = line.split("=", 1)
275 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700276 return d
277
Tao Baod1de6f32017-03-01 16:38:48 -0800278
Tianjie Xucfa86222016-03-07 16:31:19 -0800279def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
280 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700281 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800282 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700283 self.mount_point = mount_point
284 self.fs_type = fs_type
285 self.device = device
286 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700287 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700288
289 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800290 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700291 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800292 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700293 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700294
Tao Baod1de6f32017-03-01 16:38:48 -0800295 assert fstab_version == 2
296
297 d = {}
298 for line in data.split("\n"):
299 line = line.strip()
300 if not line or line.startswith("#"):
301 continue
302
303 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
304 pieces = line.split()
305 if len(pieces) != 5:
306 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
307
308 # Ignore entries that are managed by vold.
309 options = pieces[4]
310 if "voldmanaged=" in options:
311 continue
312
313 # It's a good line, parse it.
314 length = 0
315 options = options.split(",")
316 for i in options:
317 if i.startswith("length="):
318 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800319 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800320 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700321 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800322
Tao Baod1de6f32017-03-01 16:38:48 -0800323 mount_flags = pieces[3]
324 # Honor the SELinux context if present.
325 context = None
326 for i in mount_flags.split(","):
327 if i.startswith("context="):
328 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800329
Tao Baod1de6f32017-03-01 16:38:48 -0800330 mount_point = pieces[1]
331 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
332 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800333
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700334 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700335 # system. Other areas assume system is always at "/system" so point /system
336 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700337 if system_root_image:
338 assert not d.has_key("/system") and d.has_key("/")
339 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700340 return d
341
342
Doug Zongker37974732010-09-16 17:44:38 -0700343def DumpInfoDict(d):
344 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800345 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700346
Dan Albert8b72aef2015-03-23 19:13:21 -0700347
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400348def AppendAVBSigningArgs(cmd):
349 """Append signing arguments for avbtool."""
350 keypath = OPTIONS.info_dict.get("board_avb_key_path", None)
351 algorithm = OPTIONS.info_dict.get("board_avb_algorithm", None)
352 if not keypath or not algorithm:
353 algorithm = "SHA256_RSA4096"
354 keypath = "external/avb/test/data/testkey_rsa4096.pem"
355 cmd.extend(["--key", keypath, "--algorithm", algorithm])
356
357
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700358def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800359 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700360 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700361
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700362 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800363 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
364 we are building a two-step special image (i.e. building a recovery image to
365 be loaded into /boot in two-step OTAs).
366
367 Return the image data, or None if sourcedir does not appear to contains files
368 for building the requested image.
369 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700370
371 def make_ramdisk():
372 ramdisk_img = tempfile.NamedTemporaryFile()
373
374 if os.access(fs_config_file, os.F_OK):
375 cmd = ["mkbootfs", "-f", fs_config_file,
376 os.path.join(sourcedir, "RAMDISK")]
377 else:
378 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
379 p1 = Run(cmd, stdout=subprocess.PIPE)
380 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
381
382 p2.wait()
383 p1.wait()
384 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
385 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
386
387 return ramdisk_img
388
389 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
390 return None
391
392 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700393 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700394
Doug Zongkerd5131602012-08-02 14:46:42 -0700395 if info_dict is None:
396 info_dict = OPTIONS.info_dict
397
Doug Zongkereef39442009-04-02 12:14:19 -0700398 img = tempfile.NamedTemporaryFile()
399
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700400 if has_ramdisk:
401 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700402
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800403 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
404 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
405
406 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700407
Benoit Fradina45a8682014-07-14 21:00:43 +0200408 fn = os.path.join(sourcedir, "second")
409 if os.access(fn, os.F_OK):
410 cmd.append("--second")
411 cmd.append(fn)
412
Doug Zongker171f1cd2009-06-15 22:36:37 -0700413 fn = os.path.join(sourcedir, "cmdline")
414 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700415 cmd.append("--cmdline")
416 cmd.append(open(fn).read().rstrip("\n"))
417
418 fn = os.path.join(sourcedir, "base")
419 if os.access(fn, os.F_OK):
420 cmd.append("--base")
421 cmd.append(open(fn).read().rstrip("\n"))
422
Ying Wang4de6b5b2010-08-25 14:29:34 -0700423 fn = os.path.join(sourcedir, "pagesize")
424 if os.access(fn, os.F_OK):
425 cmd.append("--pagesize")
426 cmd.append(open(fn).read().rstrip("\n"))
427
Doug Zongkerd5131602012-08-02 14:46:42 -0700428 args = info_dict.get("mkbootimg_args", None)
429 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700430 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700431
Sami Tolvanen3303d902016-03-15 16:49:30 +0000432 args = info_dict.get("mkbootimg_version_args", None)
433 if args and args.strip():
434 cmd.extend(shlex.split(args))
435
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700436 if has_ramdisk:
437 cmd.extend(["--ramdisk", ramdisk_img.name])
438
Tao Baod95e9fd2015-03-29 23:07:41 -0700439 img_unsigned = None
440 if info_dict.get("vboot", None):
441 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700442 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700443 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700444 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700445
446 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700447 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700448 assert p.returncode == 0, "mkbootimg of %s image failed" % (
449 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700450
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100451 if (info_dict.get("boot_signer", None) == "true" and
452 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800453 # Hard-code the path as "/boot" for two-step special recovery image (which
454 # will be loaded into /boot during the two-step OTA).
455 if two_step_image:
456 path = "/boot"
457 else:
458 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700459 cmd = [OPTIONS.boot_signer_path]
460 cmd.extend(OPTIONS.boot_signer_args)
461 cmd.extend([path, img.name,
462 info_dict["verity_key"] + ".pk8",
463 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700464 p = Run(cmd, stdout=subprocess.PIPE)
465 p.communicate()
466 assert p.returncode == 0, "boot_signer of %s image failed" % path
467
Tao Baod95e9fd2015-03-29 23:07:41 -0700468 # Sign the image if vboot is non-empty.
469 elif info_dict.get("vboot", None):
470 path = "/" + os.path.basename(sourcedir).lower()
471 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800472 # We have switched from the prebuilt futility binary to using the tool
473 # (futility-host) built from the source. Override the setting in the old
474 # TF.zip.
475 futility = info_dict["futility"]
476 if futility.startswith("prebuilts/"):
477 futility = "futility-host"
478 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700479 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700480 info_dict["vboot_key"] + ".vbprivk",
481 info_dict["vboot_subkey"] + ".vbprivk",
482 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700483 img.name]
484 p = Run(cmd, stdout=subprocess.PIPE)
485 p.communicate()
486 assert p.returncode == 0, "vboot_signer of %s image failed" % path
487
Tao Baof3282b42015-04-01 11:21:55 -0700488 # Clean up the temp files.
489 img_unsigned.close()
490 img_keyblock.close()
491
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400492 # AVB: if enabled, calculate and add hash to boot.img.
Tao Baob31b94e2016-09-29 21:59:06 -0700493 if info_dict.get("board_avb_enable", None) == "true":
Tao Bao3cba3742017-05-16 16:27:25 -0700494 avbtool = os.getenv('AVBTOOL') or OPTIONS.info_dict["avb_avbtool"]
Tao Baob31b94e2016-09-29 21:59:06 -0700495 part_size = info_dict.get("boot_size", None)
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400496 cmd = [avbtool, "add_hash_footer", "--image", img.name,
497 "--partition_size", str(part_size), "--partition_name", "boot"]
498 AppendAVBSigningArgs(cmd)
Tao Baob31b94e2016-09-29 21:59:06 -0700499 args = info_dict.get("board_avb_boot_add_hash_footer_args", None)
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400500 if args and args.strip():
501 cmd.extend(shlex.split(args))
502 p = Run(cmd, stdout=subprocess.PIPE)
503 p.communicate()
504 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
505 os.path.basename(OPTIONS.input_tmp))
David Zeuthend995f4b2016-01-29 16:59:17 -0500506
507 img.seek(os.SEEK_SET, 0)
508 data = img.read()
509
510 if has_ramdisk:
511 ramdisk_img.close()
512 img.close()
513
514 return data
515
516
Doug Zongkerd5131602012-08-02 14:46:42 -0700517def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800518 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700519 """Return a File object with the desired bootable image.
520
521 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
522 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
523 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700524
Doug Zongker55d93282011-01-25 17:03:34 -0800525 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
526 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800527 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800528 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700529
530 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
531 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800532 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700533 return File.FromLocalFile(name, prebuilt_path)
534
Tao Bao89fbb0f2017-01-10 10:47:58 -0800535 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700536
537 if info_dict is None:
538 info_dict = OPTIONS.info_dict
539
540 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800541 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
542 # for recovery.
543 has_ramdisk = (info_dict.get("system_root_image") != "true" or
544 prebuilt_name != "boot.img" or
545 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700546
Doug Zongker6f1d0312014-08-22 08:07:12 -0700547 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400548 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
549 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800550 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700551 if data:
552 return File(name, data)
553 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800554
Doug Zongkereef39442009-04-02 12:14:19 -0700555
Doug Zongker75f17362009-12-08 13:46:44 -0800556def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800557 """Unzip the given archive into a temporary directory and return the name.
558
559 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
560 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
561
562 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
563 main file), open for reading.
564 """
Doug Zongkereef39442009-04-02 12:14:19 -0700565
566 tmp = tempfile.mkdtemp(prefix="targetfiles-")
567 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800568
569 def unzip_to_dir(filename, dirname):
570 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
571 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800572 cmd.extend(pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800573 p = Run(cmd, stdout=subprocess.PIPE)
574 p.communicate()
575 if p.returncode != 0:
576 raise ExternalError("failed to unzip input target-files \"%s\"" %
577 (filename,))
578
579 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
580 if m:
581 unzip_to_dir(m.group(1), tmp)
582 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
583 filename = m.group(1)
584 else:
585 unzip_to_dir(filename, tmp)
586
587 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700588
589
590def GetKeyPasswords(keylist):
591 """Given a list of keys, prompt the user to enter passwords for
592 those which require them. Return a {key: password} dict. password
593 will be None if the key has no password."""
594
Doug Zongker8ce7c252009-05-22 13:34:54 -0700595 no_passwords = []
596 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700597 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700598 devnull = open("/dev/null", "w+b")
599 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800600 # We don't need a password for things that aren't really keys.
601 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700602 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700603 continue
604
T.R. Fullhart37e10522013-03-18 10:31:26 -0700605 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700606 "-inform", "DER", "-nocrypt"],
607 stdin=devnull.fileno(),
608 stdout=devnull.fileno(),
609 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700610 p.communicate()
611 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700612 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700613 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700614 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700615 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
616 "-inform", "DER", "-passin", "pass:"],
617 stdin=devnull.fileno(),
618 stdout=devnull.fileno(),
619 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700620 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700621 if p.returncode == 0:
622 # Encrypted key with empty string as password.
623 key_passwords[k] = ''
624 elif stderr.startswith('Error decrypting key'):
625 # Definitely encrypted key.
626 # It would have said "Error reading key" if it didn't parse correctly.
627 need_passwords.append(k)
628 else:
629 # Potentially, a type of key that openssl doesn't understand.
630 # We'll let the routines in signapk.jar handle it.
631 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700632 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700633
T.R. Fullhart37e10522013-03-18 10:31:26 -0700634 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700635 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700636 return key_passwords
637
638
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800639def GetMinSdkVersion(apk_name):
640 """Get the minSdkVersion delared in the APK. This can be both a decimal number
641 (API Level) or a codename.
642 """
643
644 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
645 output, err = p.communicate()
646 if err:
647 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
648 % (p.returncode,))
649
650 for line in output.split("\n"):
651 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
652 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
653 if m:
654 return m.group(1)
655 raise ExternalError("No minSdkVersion returned by aapt")
656
657
658def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
659 """Get the minSdkVersion declared in the APK as a number (API Level). If
660 minSdkVersion is set to a codename, it is translated to a number using the
661 provided map.
662 """
663
664 version = GetMinSdkVersion(apk_name)
665 try:
666 return int(version)
667 except ValueError:
668 # Not a decimal number. Codename?
669 if version in codename_to_api_level_map:
670 return codename_to_api_level_map[version]
671 else:
672 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
673 % (version, codename_to_api_level_map))
674
675
676def SignFile(input_name, output_name, key, password, min_api_level=None,
677 codename_to_api_level_map=dict(),
678 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700679 """Sign the input_name zip/jar/apk, producing output_name. Use the
680 given key and password (the latter may be None if the key does not
681 have a password.
682
Doug Zongker951495f2009-08-14 12:44:19 -0700683 If whole_file is true, use the "-w" option to SignApk to embed a
684 signature that covers the whole file in the archive comment of the
685 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800686
687 min_api_level is the API Level (int) of the oldest platform this file may end
688 up on. If not specified for an APK, the API Level is obtained by interpreting
689 the minSdkVersion attribute of the APK's AndroidManifest.xml.
690
691 codename_to_api_level_map is needed to translate the codename which may be
692 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700693 """
Doug Zongker951495f2009-08-14 12:44:19 -0700694
Alex Klyubin9667b182015-12-10 13:38:50 -0800695 java_library_path = os.path.join(
696 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
697
Tao Baoe95540e2016-11-08 12:08:53 -0800698 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
699 ["-Djava.library.path=" + java_library_path,
700 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
701 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700702 if whole_file:
703 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800704
705 min_sdk_version = min_api_level
706 if min_sdk_version is None:
707 if not whole_file:
708 min_sdk_version = GetMinSdkVersionInt(
709 input_name, codename_to_api_level_map)
710 if min_sdk_version is not None:
711 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
712
T.R. Fullhart37e10522013-03-18 10:31:26 -0700713 cmd.extend([key + OPTIONS.public_key_suffix,
714 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800715 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700716
717 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700718 if password is not None:
719 password += "\n"
720 p.communicate(password)
721 if p.returncode != 0:
722 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
723
Doug Zongkereef39442009-04-02 12:14:19 -0700724
Doug Zongker37974732010-09-16 17:44:38 -0700725def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700726 """Check the data string passed against the max size limit, if
727 any, for the given target. Raise exception if the data is too big.
728 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700729
Dan Albert8b72aef2015-03-23 19:13:21 -0700730 if target.endswith(".img"):
731 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700732 mount_point = "/" + target
733
Ying Wangf8824af2014-06-03 14:07:27 -0700734 fs_type = None
735 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700736 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700737 if mount_point == "/userdata":
738 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700739 p = info_dict["fstab"][mount_point]
740 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800741 device = p.device
742 if "/" in device:
743 device = device[device.rfind("/")+1:]
744 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700745 if not fs_type or not limit:
746 return
Doug Zongkereef39442009-04-02 12:14:19 -0700747
Andrew Boie0f9aec82012-02-14 09:32:52 -0800748 size = len(data)
749 pct = float(size) * 100.0 / limit
750 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
751 if pct >= 99.0:
752 raise ExternalError(msg)
753 elif pct >= 95.0:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800754 print("\n WARNING: %s\n" % (msg,))
Andrew Boie0f9aec82012-02-14 09:32:52 -0800755 elif OPTIONS.verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800756 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700757
758
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800759def ReadApkCerts(tf_zip):
760 """Given a target_files ZipFile, parse the META/apkcerts.txt file
761 and return a {package: cert} dict."""
762 certmap = {}
763 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
764 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700765 if not line:
766 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800767 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
768 r'private_key="(.*)"$', line)
769 if m:
770 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700771 public_key_suffix_len = len(OPTIONS.public_key_suffix)
772 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800773 if cert in SPECIAL_CERT_STRINGS and not privkey:
774 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700775 elif (cert.endswith(OPTIONS.public_key_suffix) and
776 privkey.endswith(OPTIONS.private_key_suffix) and
777 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
778 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800779 else:
780 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
781 return certmap
782
783
Doug Zongkereef39442009-04-02 12:14:19 -0700784COMMON_DOCSTRING = """
785 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700786 Prepend <dir>/bin to the list of places to search for binaries
787 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700788
Doug Zongker05d3dea2009-06-22 11:32:31 -0700789 -s (--device_specific) <file>
790 Path to the python module containing device-specific
791 releasetools code.
792
Doug Zongker8bec09e2009-11-30 15:37:14 -0800793 -x (--extra) <key=value>
794 Add a key/value pair to the 'extras' dict, which device-specific
795 extension code may look at.
796
Doug Zongkereef39442009-04-02 12:14:19 -0700797 -v (--verbose)
798 Show command lines being executed.
799
800 -h (--help)
801 Display this usage message and exit.
802"""
803
804def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800805 print(docstring.rstrip("\n"))
806 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700807
808
809def ParseOptions(argv,
810 docstring,
811 extra_opts="", extra_long_opts=(),
812 extra_option_handler=None):
813 """Parse the options in argv and return any arguments that aren't
814 flags. docstring is the calling module's docstring, to be displayed
815 for errors and -h. extra_opts and extra_long_opts are for flags
816 defined by the caller, which are processed by passing them to
817 extra_option_handler."""
818
819 try:
820 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800821 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800822 ["help", "verbose", "path=", "signapk_path=",
823 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700824 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700825 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
826 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800827 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700828 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700829 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700830 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800831 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700832 sys.exit(2)
833
Doug Zongkereef39442009-04-02 12:14:19 -0700834 for o, a in opts:
835 if o in ("-h", "--help"):
836 Usage(docstring)
837 sys.exit()
838 elif o in ("-v", "--verbose"):
839 OPTIONS.verbose = True
840 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700841 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700842 elif o in ("--signapk_path",):
843 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800844 elif o in ("--signapk_shared_library_path",):
845 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700846 elif o in ("--extra_signapk_args",):
847 OPTIONS.extra_signapk_args = shlex.split(a)
848 elif o in ("--java_path",):
849 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700850 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800851 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700852 elif o in ("--public_key_suffix",):
853 OPTIONS.public_key_suffix = a
854 elif o in ("--private_key_suffix",):
855 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800856 elif o in ("--boot_signer_path",):
857 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700858 elif o in ("--boot_signer_args",):
859 OPTIONS.boot_signer_args = shlex.split(a)
860 elif o in ("--verity_signer_path",):
861 OPTIONS.verity_signer_path = a
862 elif o in ("--verity_signer_args",):
863 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700864 elif o in ("-s", "--device_specific"):
865 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800866 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800867 key, value = a.split("=", 1)
868 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700869 else:
870 if extra_option_handler is None or not extra_option_handler(o, a):
871 assert False, "unknown option \"%s\"" % (o,)
872
Doug Zongker85448772014-09-09 14:59:20 -0700873 if OPTIONS.search_path:
874 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
875 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700876
877 return args
878
879
Tao Bao4c851b12016-09-19 13:54:38 -0700880def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700881 """Make a temp file and add it to the list of things to be deleted
882 when Cleanup() is called. Return the filename."""
883 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
884 os.close(fd)
885 OPTIONS.tempfiles.append(fn)
886 return fn
887
888
Doug Zongkereef39442009-04-02 12:14:19 -0700889def Cleanup():
890 for i in OPTIONS.tempfiles:
891 if os.path.isdir(i):
892 shutil.rmtree(i)
893 else:
894 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700895
896
897class PasswordManager(object):
898 def __init__(self):
899 self.editor = os.getenv("EDITOR", None)
900 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
901
902 def GetPasswords(self, items):
903 """Get passwords corresponding to each string in 'items',
904 returning a dict. (The dict may have keys in addition to the
905 values in 'items'.)
906
907 Uses the passwords in $ANDROID_PW_FILE if available, letting the
908 user edit that file to add more needed passwords. If no editor is
909 available, or $ANDROID_PW_FILE isn't define, prompts the user
910 interactively in the ordinary way.
911 """
912
913 current = self.ReadFile()
914
915 first = True
916 while True:
917 missing = []
918 for i in items:
919 if i not in current or not current[i]:
920 missing.append(i)
921 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700922 if not missing:
923 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700924
925 for i in missing:
926 current[i] = ""
927
928 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800929 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700930 answer = raw_input("try to edit again? [y]> ").strip()
931 if answer and answer[0] not in 'yY':
932 raise RuntimeError("key passwords unavailable")
933 first = False
934
935 current = self.UpdateAndReadFile(current)
936
Dan Albert8b72aef2015-03-23 19:13:21 -0700937 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700938 """Prompt the user to enter a value (password) for each key in
939 'current' whose value is fales. Returns a new dict with all the
940 values.
941 """
942 result = {}
943 for k, v in sorted(current.iteritems()):
944 if v:
945 result[k] = v
946 else:
947 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700948 result[k] = getpass.getpass(
949 "Enter password for %s key> " % k).strip()
950 if result[k]:
951 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700952 return result
953
954 def UpdateAndReadFile(self, current):
955 if not self.editor or not self.pwfile:
956 return self.PromptResult(current)
957
958 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700959 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700960 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
961 f.write("# (Additional spaces are harmless.)\n\n")
962
963 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700964 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
965 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700966 f.write("[[[ %s ]]] %s\n" % (v, k))
967 if not v and first_line is None:
968 # position cursor on first line with no password.
969 first_line = i + 4
970 f.close()
971
972 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
973 _, _ = p.communicate()
974
975 return self.ReadFile()
976
977 def ReadFile(self):
978 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700979 if self.pwfile is None:
980 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700981 try:
982 f = open(self.pwfile, "r")
983 for line in f:
984 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700985 if not line or line[0] == '#':
986 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700987 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
988 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800989 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700990 else:
991 result[m.group(2)] = m.group(1)
992 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700993 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700994 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800995 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700996 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700997
998
Dan Albert8e0178d2015-01-27 15:53:15 -0800999def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1000 compress_type=None):
1001 import datetime
1002
1003 # http://b/18015246
1004 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1005 # for files larger than 2GiB. We can work around this by adjusting their
1006 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1007 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1008 # it isn't clear to me exactly what circumstances cause this).
1009 # `zipfile.write()` must be used directly to work around this.
1010 #
1011 # This mess can be avoided if we port to python3.
1012 saved_zip64_limit = zipfile.ZIP64_LIMIT
1013 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1014
1015 if compress_type is None:
1016 compress_type = zip_file.compression
1017 if arcname is None:
1018 arcname = filename
1019
1020 saved_stat = os.stat(filename)
1021
1022 try:
1023 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1024 # file to be zipped and reset it when we're done.
1025 os.chmod(filename, perms)
1026
1027 # Use a fixed timestamp so the output is repeatable.
1028 epoch = datetime.datetime.fromtimestamp(0)
1029 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1030 os.utime(filename, (timestamp, timestamp))
1031
1032 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1033 finally:
1034 os.chmod(filename, saved_stat.st_mode)
1035 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1036 zipfile.ZIP64_LIMIT = saved_zip64_limit
1037
1038
Tao Bao58c1b962015-05-20 09:32:18 -07001039def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001040 compress_type=None):
1041 """Wrap zipfile.writestr() function to work around the zip64 limit.
1042
1043 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1044 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1045 when calling crc32(bytes).
1046
1047 But it still works fine to write a shorter string into a large zip file.
1048 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1049 when we know the string won't be too long.
1050 """
1051
1052 saved_zip64_limit = zipfile.ZIP64_LIMIT
1053 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1054
1055 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1056 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001057 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001058 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001059 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001060 else:
Tao Baof3282b42015-04-01 11:21:55 -07001061 zinfo = zinfo_or_arcname
1062
1063 # If compress_type is given, it overrides the value in zinfo.
1064 if compress_type is not None:
1065 zinfo.compress_type = compress_type
1066
Tao Bao58c1b962015-05-20 09:32:18 -07001067 # If perms is given, it has a priority.
1068 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001069 # If perms doesn't set the file type, mark it as a regular file.
1070 if perms & 0o770000 == 0:
1071 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001072 zinfo.external_attr = perms << 16
1073
Tao Baof3282b42015-04-01 11:21:55 -07001074 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001075 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1076
Dan Albert8b72aef2015-03-23 19:13:21 -07001077 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001078 zipfile.ZIP64_LIMIT = saved_zip64_limit
1079
1080
1081def ZipClose(zip_file):
1082 # http://b/18015246
1083 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1084 # central directory.
1085 saved_zip64_limit = zipfile.ZIP64_LIMIT
1086 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1087
1088 zip_file.close()
1089
1090 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001091
1092
1093class DeviceSpecificParams(object):
1094 module = None
1095 def __init__(self, **kwargs):
1096 """Keyword arguments to the constructor become attributes of this
1097 object, which is passed to all functions in the device-specific
1098 module."""
1099 for k, v in kwargs.iteritems():
1100 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001101 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001102
1103 if self.module is None:
1104 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001105 if not path:
1106 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001107 try:
1108 if os.path.isdir(path):
1109 info = imp.find_module("releasetools", [path])
1110 else:
1111 d, f = os.path.split(path)
1112 b, x = os.path.splitext(f)
1113 if x == ".py":
1114 f = b
1115 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001116 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001117 self.module = imp.load_module("device_specific", *info)
1118 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001119 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001120
1121 def _DoCall(self, function_name, *args, **kwargs):
1122 """Call the named function in the device-specific module, passing
1123 the given args and kwargs. The first argument to the call will be
1124 the DeviceSpecific object itself. If there is no module, or the
1125 module does not define the function, return the value of the
1126 'default' kwarg (which itself defaults to None)."""
1127 if self.module is None or not hasattr(self.module, function_name):
1128 return kwargs.get("default", None)
1129 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1130
1131 def FullOTA_Assertions(self):
1132 """Called after emitting the block of assertions at the top of a
1133 full OTA package. Implementations can add whatever additional
1134 assertions they like."""
1135 return self._DoCall("FullOTA_Assertions")
1136
Doug Zongkere5ff5902012-01-17 10:55:37 -08001137 def FullOTA_InstallBegin(self):
1138 """Called at the start of full OTA installation."""
1139 return self._DoCall("FullOTA_InstallBegin")
1140
Doug Zongker05d3dea2009-06-22 11:32:31 -07001141 def FullOTA_InstallEnd(self):
1142 """Called at the end of full OTA installation; typically this is
1143 used to install the image for the device's baseband processor."""
1144 return self._DoCall("FullOTA_InstallEnd")
1145
1146 def IncrementalOTA_Assertions(self):
1147 """Called after emitting the block of assertions at the top of an
1148 incremental OTA package. Implementations can add whatever
1149 additional assertions they like."""
1150 return self._DoCall("IncrementalOTA_Assertions")
1151
Doug Zongkere5ff5902012-01-17 10:55:37 -08001152 def IncrementalOTA_VerifyBegin(self):
1153 """Called at the start of the verification phase of incremental
1154 OTA installation; additional checks can be placed here to abort
1155 the script before any changes are made."""
1156 return self._DoCall("IncrementalOTA_VerifyBegin")
1157
Doug Zongker05d3dea2009-06-22 11:32:31 -07001158 def IncrementalOTA_VerifyEnd(self):
1159 """Called at the end of the verification phase of incremental OTA
1160 installation; additional checks can be placed here to abort the
1161 script before any changes are made."""
1162 return self._DoCall("IncrementalOTA_VerifyEnd")
1163
Doug Zongkere5ff5902012-01-17 10:55:37 -08001164 def IncrementalOTA_InstallBegin(self):
1165 """Called at the start of incremental OTA installation (after
1166 verification is complete)."""
1167 return self._DoCall("IncrementalOTA_InstallBegin")
1168
Doug Zongker05d3dea2009-06-22 11:32:31 -07001169 def IncrementalOTA_InstallEnd(self):
1170 """Called at the end of incremental OTA installation; typically
1171 this is used to install the image for the device's baseband
1172 processor."""
1173 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001174
Tao Bao9bc6bb22015-11-09 16:58:28 -08001175 def VerifyOTA_Assertions(self):
1176 return self._DoCall("VerifyOTA_Assertions")
1177
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001178class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001179 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001180 self.name = name
1181 self.data = data
1182 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001183 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001184 self.sha1 = sha1(data).hexdigest()
1185
1186 @classmethod
1187 def FromLocalFile(cls, name, diskname):
1188 f = open(diskname, "rb")
1189 data = f.read()
1190 f.close()
1191 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001192
1193 def WriteToTemp(self):
1194 t = tempfile.NamedTemporaryFile()
1195 t.write(self.data)
1196 t.flush()
1197 return t
1198
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001199 def WriteToDir(self, d):
1200 with open(os.path.join(d, self.name), "wb") as fp:
1201 fp.write(self.data)
1202
Geremy Condra36bd3652014-02-06 19:45:10 -08001203 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001204 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001205
1206DIFF_PROGRAM_BY_EXT = {
1207 ".gz" : "imgdiff",
1208 ".zip" : ["imgdiff", "-z"],
1209 ".jar" : ["imgdiff", "-z"],
1210 ".apk" : ["imgdiff", "-z"],
1211 ".img" : "imgdiff",
1212 }
1213
1214class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001215 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001216 self.tf = tf
1217 self.sf = sf
1218 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001219 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001220
1221 def ComputePatch(self):
1222 """Compute the patch (as a string of data) needed to turn sf into
1223 tf. Returns the same tuple as GetPatch()."""
1224
1225 tf = self.tf
1226 sf = self.sf
1227
Doug Zongker24cd2802012-08-14 16:36:15 -07001228 if self.diff_program:
1229 diff_program = self.diff_program
1230 else:
1231 ext = os.path.splitext(tf.name)[1]
1232 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001233
1234 ttemp = tf.WriteToTemp()
1235 stemp = sf.WriteToTemp()
1236
1237 ext = os.path.splitext(tf.name)[1]
1238
1239 try:
1240 ptemp = tempfile.NamedTemporaryFile()
1241 if isinstance(diff_program, list):
1242 cmd = copy.copy(diff_program)
1243 else:
1244 cmd = [diff_program]
1245 cmd.append(stemp.name)
1246 cmd.append(ttemp.name)
1247 cmd.append(ptemp.name)
1248 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001249 err = []
1250 def run():
1251 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001252 if e:
1253 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001254 th = threading.Thread(target=run)
1255 th.start()
1256 th.join(timeout=300) # 5 mins
1257 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001258 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001259 p.terminate()
1260 th.join(5)
1261 if th.is_alive():
1262 p.kill()
1263 th.join()
1264
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001265 if err or p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001266 print("WARNING: failure running %s:\n%s\n" % (
1267 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001268 self.patch = None
1269 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001270 diff = ptemp.read()
1271 finally:
1272 ptemp.close()
1273 stemp.close()
1274 ttemp.close()
1275
1276 self.patch = diff
1277 return self.tf, self.sf, self.patch
1278
1279
1280 def GetPatch(self):
1281 """Return a tuple (target_file, source_file, patch_data).
1282 patch_data may be None if ComputePatch hasn't been called, or if
1283 computing the patch failed."""
1284 return self.tf, self.sf, self.patch
1285
1286
1287def ComputeDifferences(diffs):
1288 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001289 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001290
1291 # Do the largest files first, to try and reduce the long-pole effect.
1292 by_size = [(i.tf.size, i) for i in diffs]
1293 by_size.sort(reverse=True)
1294 by_size = [i[1] for i in by_size]
1295
1296 lock = threading.Lock()
1297 diff_iter = iter(by_size) # accessed under lock
1298
1299 def worker():
1300 try:
1301 lock.acquire()
1302 for d in diff_iter:
1303 lock.release()
1304 start = time.time()
1305 d.ComputePatch()
1306 dur = time.time() - start
1307 lock.acquire()
1308
1309 tf, sf, patch = d.GetPatch()
1310 if sf.name == tf.name:
1311 name = tf.name
1312 else:
1313 name = "%s (%s)" % (tf.name, sf.name)
1314 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001315 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001316 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001317 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1318 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001319 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001320 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001321 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001322 raise
1323
1324 # start worker threads; wait for them all to finish.
1325 threads = [threading.Thread(target=worker)
1326 for i in range(OPTIONS.worker_threads)]
1327 for th in threads:
1328 th.start()
1329 while threads:
1330 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001331
1332
Dan Albert8b72aef2015-03-23 19:13:21 -07001333class BlockDifference(object):
1334 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001335 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001336 self.tgt = tgt
1337 self.src = src
1338 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001339 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001340 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001341
Tao Baodd2a5892015-03-12 12:32:37 -07001342 if version is None:
1343 version = 1
1344 if OPTIONS.info_dict:
1345 version = max(
1346 int(i) for i in
1347 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001348 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001349 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001350
1351 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001352 version=self.version,
1353 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001354 tmpdir = tempfile.mkdtemp()
1355 OPTIONS.tempfiles.append(tmpdir)
1356 self.path = os.path.join(tmpdir, partition)
1357 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001358 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001359 self.touched_src_ranges = b.touched_src_ranges
1360 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001361
Tao Baoaac4ad52015-10-16 15:26:34 -07001362 if src is None:
1363 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1364 else:
1365 _, self.device = GetTypeAndDevice("/" + partition,
1366 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001367
Tao Baod8d14be2016-02-04 14:26:02 -08001368 @property
1369 def required_cache(self):
1370 return self._required_cache
1371
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001372 def WriteScript(self, script, output_zip, progress=None):
1373 if not self.src:
1374 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001375 script.Print("Patching %s image unconditionally..." % (self.partition,))
1376 else:
1377 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001378
Dan Albert8b72aef2015-03-23 19:13:21 -07001379 if progress:
1380 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001381 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001382 if OPTIONS.verify:
1383 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001384
Tao Bao9bc6bb22015-11-09 16:58:28 -08001385 def WriteStrictVerifyScript(self, script):
1386 """Verify all the blocks in the care_map, including clobbered blocks.
1387
1388 This differs from the WriteVerifyScript() function: a) it prints different
1389 error messages; b) it doesn't allow half-way updated images to pass the
1390 verification."""
1391
1392 partition = self.partition
1393 script.Print("Verifying %s..." % (partition,))
1394 ranges = self.tgt.care_map
1395 ranges_str = ranges.to_string_raw()
1396 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1397 'ui_print(" Verified.") || '
1398 'ui_print("\\"%s\\" has unexpected contents.");' % (
1399 self.device, ranges_str,
1400 self.tgt.TotalSha1(include_clobbered_blocks=True),
1401 self.device))
1402 script.AppendExtra("")
1403
Tao Baod522bdc2016-04-12 15:53:16 -07001404 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001405 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001406
1407 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001408 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001409 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001410
1411 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001412 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001413 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001414 ranges = self.touched_src_ranges
1415 expected_sha1 = self.touched_src_sha1
1416 else:
1417 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1418 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001419
1420 # No blocks to be checked, skipping.
1421 if not ranges:
1422 return
1423
Tao Bao5ece99d2015-05-12 11:42:31 -07001424 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001425 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1426 'block_image_verify("%s", '
1427 'package_extract_file("%s.transfer.list"), '
1428 '"%s.new.dat", "%s.patch.dat")) then') % (
1429 self.device, ranges_str, expected_sha1,
1430 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001431 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001432 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001433
Tianjie Xufc3422a2015-12-15 11:53:59 -08001434 if self.version >= 4:
1435
1436 # Bug: 21124327
1437 # When generating incrementals for the system and vendor partitions in
1438 # version 4 or newer, explicitly check the first block (which contains
1439 # the superblock) of the partition to see if it's what we expect. If
1440 # this check fails, give an explicit log message about the partition
1441 # having been remounted R/W (the most likely explanation).
1442 if self.check_first_block:
1443 script.AppendExtra('check_first_block("%s");' % (self.device,))
1444
1445 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001446 if partition == "system":
1447 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1448 else:
1449 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001450 script.AppendExtra((
1451 'ifelse (block_image_recover("{device}", "{ranges}") && '
1452 'block_image_verify("{device}", '
1453 'package_extract_file("{partition}.transfer.list"), '
1454 '"{partition}.new.dat", "{partition}.patch.dat"), '
1455 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001456 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001457 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001458 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001459
Tao Baodd2a5892015-03-12 12:32:37 -07001460 # Abort the OTA update. Note that the incremental OTA cannot be applied
1461 # even if it may match the checksum of the target partition.
1462 # a) If version < 3, operations like move and erase will make changes
1463 # unconditionally and damage the partition.
1464 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001465 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001466 if partition == "system":
1467 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1468 else:
1469 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1470 script.AppendExtra((
1471 'abort("E%d: %s partition has unexpected contents");\n'
1472 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001473
Tao Bao5fcaaef2015-06-01 13:40:49 -07001474 def _WritePostInstallVerifyScript(self, script):
1475 partition = self.partition
1476 script.Print('Verifying the updated %s image...' % (partition,))
1477 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1478 ranges = self.tgt.care_map
1479 ranges_str = ranges.to_string_raw()
1480 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1481 self.device, ranges_str,
1482 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001483
1484 # Bug: 20881595
1485 # Verify that extended blocks are really zeroed out.
1486 if self.tgt.extended:
1487 ranges_str = self.tgt.extended.to_string_raw()
1488 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1489 self.device, ranges_str,
1490 self._HashZeroBlocks(self.tgt.extended.size())))
1491 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001492 if partition == "system":
1493 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1494 else:
1495 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001496 script.AppendExtra(
1497 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001498 ' abort("E%d: %s partition has unexpected non-zero contents after '
1499 'OTA update");\n'
1500 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001501 else:
1502 script.Print('Verified the updated %s image.' % (partition,))
1503
Tianjie Xu209db462016-05-24 17:34:52 -07001504 if partition == "system":
1505 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1506 else:
1507 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1508
Tao Bao5fcaaef2015-06-01 13:40:49 -07001509 script.AppendExtra(
1510 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001511 ' abort("E%d: %s partition has unexpected contents after OTA '
1512 'update");\n'
1513 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001514
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001515 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001516 ZipWrite(output_zip,
1517 '{}.transfer.list'.format(self.path),
1518 '{}.transfer.list'.format(self.partition))
1519 ZipWrite(output_zip,
1520 '{}.new.dat'.format(self.path),
1521 '{}.new.dat'.format(self.partition))
1522 ZipWrite(output_zip,
1523 '{}.patch.dat'.format(self.path),
1524 '{}.patch.dat'.format(self.partition),
1525 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001526
Tianjie Xu209db462016-05-24 17:34:52 -07001527 if self.partition == "system":
1528 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1529 else:
1530 code = ErrorCode.VENDOR_UPDATE_FAILURE
1531
Dan Albert8e0178d2015-01-27 15:53:15 -08001532 call = ('block_image_update("{device}", '
1533 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub2deb222016-03-25 15:01:33 -07001534 '"{partition}.new.dat", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001535 ' abort("E{code}: Failed to update {partition} image.");'.format(
1536 device=self.device, partition=self.partition, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001537 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001538
Dan Albert8b72aef2015-03-23 19:13:21 -07001539 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001540 data = source.ReadRangeSet(ranges)
1541 ctx = sha1()
1542
1543 for p in data:
1544 ctx.update(p)
1545
1546 return ctx.hexdigest()
1547
Tao Baoe9b61912015-07-09 17:37:49 -07001548 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1549 """Return the hash value for all zero blocks."""
1550 zero_block = '\x00' * 4096
1551 ctx = sha1()
1552 for _ in range(num_blocks):
1553 ctx.update(zero_block)
1554
1555 return ctx.hexdigest()
1556
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001557
1558DataImage = blockimgdiff.DataImage
1559
Doug Zongker96a57e72010-09-26 14:57:41 -07001560# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001561PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001562 "ext4": "EMMC",
1563 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001564 "f2fs": "EMMC",
1565 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001566}
Doug Zongker96a57e72010-09-26 14:57:41 -07001567
1568def GetTypeAndDevice(mount_point, info):
1569 fstab = info["fstab"]
1570 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001571 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1572 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001573 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001574 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001575
1576
1577def ParseCertificate(data):
1578 """Parse a PEM-format certificate."""
1579 cert = []
1580 save = False
1581 for line in data.split("\n"):
1582 if "--END CERTIFICATE--" in line:
1583 break
1584 if save:
1585 cert.append(line)
1586 if "--BEGIN CERTIFICATE--" in line:
1587 save = True
1588 cert = "".join(cert).decode('base64')
1589 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001590
Doug Zongker412c02f2014-02-13 10:58:24 -08001591def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1592 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001593 """Generate a binary patch that creates the recovery image starting
1594 with the boot image. (Most of the space in these images is just the
1595 kernel, which is identical for the two, so the resulting patch
1596 should be efficient.) Add it to the output zip, along with a shell
1597 script that is run from init.rc on first boot to actually do the
1598 patching and install the new recovery image.
1599
1600 recovery_img and boot_img should be File objects for the
1601 corresponding images. info should be the dictionary returned by
1602 common.LoadInfoDict() on the input target_files.
1603 """
1604
Doug Zongker412c02f2014-02-13 10:58:24 -08001605 if info_dict is None:
1606 info_dict = OPTIONS.info_dict
1607
Tao Baof2cffbd2015-07-22 12:33:18 -07001608 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001609 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001610
Tao Baof2cffbd2015-07-22 12:33:18 -07001611 if full_recovery_image:
1612 output_sink("etc/recovery.img", recovery_img.data)
1613
1614 else:
1615 diff_program = ["imgdiff"]
1616 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1617 if os.path.exists(path):
1618 diff_program.append("-b")
1619 diff_program.append(path)
1620 bonus_args = "-b /system/etc/recovery-resource.dat"
1621 else:
1622 bonus_args = ""
1623
1624 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1625 _, _, patch = d.ComputePatch()
1626 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001627
Dan Albertebb19aa2015-03-27 19:11:53 -07001628 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001629 # The following GetTypeAndDevice()s need to use the path in the target
1630 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001631 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1632 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1633 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001634 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001635
Tao Baof2cffbd2015-07-22 12:33:18 -07001636 if full_recovery_image:
1637 sh = """#!/system/bin/sh
1638if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1639 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"
1640else
1641 log -t recovery "Recovery image already installed"
1642fi
1643""" % {'type': recovery_type,
1644 'device': recovery_device,
1645 'sha1': recovery_img.sha1,
1646 'size': recovery_img.size}
1647 else:
1648 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001649if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1650 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"
1651else
1652 log -t recovery "Recovery image already installed"
1653fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001654""" % {'boot_size': boot_img.size,
1655 'boot_sha1': boot_img.sha1,
1656 'recovery_size': recovery_img.size,
1657 'recovery_sha1': recovery_img.sha1,
1658 'boot_type': boot_type,
1659 'boot_device': boot_device,
1660 'recovery_type': recovery_type,
1661 'recovery_device': recovery_device,
1662 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001663
1664 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001665 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001666 # target-files expects it to be, and put it there.
1667 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001668 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001669 if system_root_image:
1670 init_rc_dir = os.path.join(input_dir, "ROOT")
1671 else:
1672 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001673 init_rc_files = os.listdir(init_rc_dir)
1674 for init_rc_file in init_rc_files:
1675 if (not init_rc_file.startswith('init.') or
1676 not init_rc_file.endswith('.rc')):
1677 continue
1678
1679 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001680 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001681 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001682 if m:
1683 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001684 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001685 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001686
1687 if found:
1688 break
1689
Tao Bao89fbb0f2017-01-10 10:47:58 -08001690 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001691
1692 output_sink(sh_location, sh)