blob: 97261255cf061b45ba39604c037b99c3f2a487cb [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
Tao Baobcd1d162017-08-26 13:10:26 -0700254 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
255 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700256 return d
257
Tao Baod1de6f32017-03-01 16:38:48 -0800258
Tao Baobcd1d162017-08-26 13:10:26 -0700259def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700260 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700261 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700262 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700263 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700264 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700265 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700266
Tao Baod1de6f32017-03-01 16:38:48 -0800267
Michael Runge6e836112014-04-15 17:40:21 -0700268def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700269 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700270 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700271 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700272 if not line or line.startswith("#"):
273 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700274 if "=" in line:
275 name, value = line.split("=", 1)
276 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700277 return d
278
Tao Baod1de6f32017-03-01 16:38:48 -0800279
Tianjie Xucfa86222016-03-07 16:31:19 -0800280def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
281 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700282 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800283 def __init__(self, mount_point, fs_type, device, length, 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
Tao Bao548eb762015-06-10 12:32:41 -0700288 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700289
290 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800291 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700292 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800293 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700294 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700295
Tao Baod1de6f32017-03-01 16:38:48 -0800296 assert fstab_version == 2
297
298 d = {}
299 for line in data.split("\n"):
300 line = line.strip()
301 if not line or line.startswith("#"):
302 continue
303
304 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
305 pieces = line.split()
306 if len(pieces) != 5:
307 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
308
309 # Ignore entries that are managed by vold.
310 options = pieces[4]
311 if "voldmanaged=" in options:
312 continue
313
314 # It's a good line, parse it.
315 length = 0
316 options = options.split(",")
317 for i in options:
318 if i.startswith("length="):
319 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800320 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800321 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700322 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800323
Tao Baod1de6f32017-03-01 16:38:48 -0800324 mount_flags = pieces[3]
325 # Honor the SELinux context if present.
326 context = None
327 for i in mount_flags.split(","):
328 if i.startswith("context="):
329 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800330
Tao Baod1de6f32017-03-01 16:38:48 -0800331 mount_point = pieces[1]
332 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
333 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800334
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700335 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700336 # system. Other areas assume system is always at "/system" so point /system
337 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700338 if system_root_image:
339 assert not d.has_key("/system") and d.has_key("/")
340 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700341 return d
342
343
Doug Zongker37974732010-09-16 17:44:38 -0700344def DumpInfoDict(d):
345 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800346 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700347
Dan Albert8b72aef2015-03-23 19:13:21 -0700348
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800349def AppendAVBSigningArgs(cmd, partition):
350 """Append signing arguments for avbtool."""
351 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
352 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
353 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
354 if key_path and algorithm:
355 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700356 avb_salt = OPTIONS.info_dict.get("avb_salt")
357 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
358 if avb_salt and partition != "vbmeta":
359 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800360
361
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700362def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800363 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700364 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700365
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700366 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800367 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
368 we are building a two-step special image (i.e. building a recovery image to
369 be loaded into /boot in two-step OTAs).
370
371 Return the image data, or None if sourcedir does not appear to contains files
372 for building the requested image.
373 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700374
375 def make_ramdisk():
376 ramdisk_img = tempfile.NamedTemporaryFile()
377
378 if os.access(fs_config_file, os.F_OK):
379 cmd = ["mkbootfs", "-f", fs_config_file,
380 os.path.join(sourcedir, "RAMDISK")]
381 else:
382 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
383 p1 = Run(cmd, stdout=subprocess.PIPE)
384 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
385
386 p2.wait()
387 p1.wait()
388 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
389 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
390
391 return ramdisk_img
392
393 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
394 return None
395
396 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700397 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700398
Doug Zongkerd5131602012-08-02 14:46:42 -0700399 if info_dict is None:
400 info_dict = OPTIONS.info_dict
401
Doug Zongkereef39442009-04-02 12:14:19 -0700402 img = tempfile.NamedTemporaryFile()
403
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700404 if has_ramdisk:
405 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700406
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800407 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
408 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
409
410 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700411
Benoit Fradina45a8682014-07-14 21:00:43 +0200412 fn = os.path.join(sourcedir, "second")
413 if os.access(fn, os.F_OK):
414 cmd.append("--second")
415 cmd.append(fn)
416
Doug Zongker171f1cd2009-06-15 22:36:37 -0700417 fn = os.path.join(sourcedir, "cmdline")
418 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700419 cmd.append("--cmdline")
420 cmd.append(open(fn).read().rstrip("\n"))
421
422 fn = os.path.join(sourcedir, "base")
423 if os.access(fn, os.F_OK):
424 cmd.append("--base")
425 cmd.append(open(fn).read().rstrip("\n"))
426
Ying Wang4de6b5b2010-08-25 14:29:34 -0700427 fn = os.path.join(sourcedir, "pagesize")
428 if os.access(fn, os.F_OK):
429 cmd.append("--pagesize")
430 cmd.append(open(fn).read().rstrip("\n"))
431
Doug Zongkerd5131602012-08-02 14:46:42 -0700432 args = info_dict.get("mkbootimg_args", None)
433 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700434 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700435
Sami Tolvanen3303d902016-03-15 16:49:30 +0000436 args = info_dict.get("mkbootimg_version_args", None)
437 if args and args.strip():
438 cmd.extend(shlex.split(args))
439
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700440 if has_ramdisk:
441 cmd.extend(["--ramdisk", ramdisk_img.name])
442
Tao Baod95e9fd2015-03-29 23:07:41 -0700443 img_unsigned = None
444 if info_dict.get("vboot", None):
445 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700446 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700447 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700448 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700449
450 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700451 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700452 assert p.returncode == 0, "mkbootimg of %s image failed" % (
453 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700454
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100455 if (info_dict.get("boot_signer", None) == "true" and
456 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800457 # Hard-code the path as "/boot" for two-step special recovery image (which
458 # will be loaded into /boot during the two-step OTA).
459 if two_step_image:
460 path = "/boot"
461 else:
462 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700463 cmd = [OPTIONS.boot_signer_path]
464 cmd.extend(OPTIONS.boot_signer_args)
465 cmd.extend([path, img.name,
466 info_dict["verity_key"] + ".pk8",
467 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700468 p = Run(cmd, stdout=subprocess.PIPE)
469 p.communicate()
470 assert p.returncode == 0, "boot_signer of %s image failed" % path
471
Tao Baod95e9fd2015-03-29 23:07:41 -0700472 # Sign the image if vboot is non-empty.
473 elif info_dict.get("vboot", None):
474 path = "/" + os.path.basename(sourcedir).lower()
475 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800476 # We have switched from the prebuilt futility binary to using the tool
477 # (futility-host) built from the source. Override the setting in the old
478 # TF.zip.
479 futility = info_dict["futility"]
480 if futility.startswith("prebuilts/"):
481 futility = "futility-host"
482 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700483 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700484 info_dict["vboot_key"] + ".vbprivk",
485 info_dict["vboot_subkey"] + ".vbprivk",
486 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700487 img.name]
488 p = Run(cmd, stdout=subprocess.PIPE)
489 p.communicate()
490 assert p.returncode == 0, "vboot_signer of %s image failed" % path
491
Tao Baof3282b42015-04-01 11:21:55 -0700492 # Clean up the temp files.
493 img_unsigned.close()
494 img_keyblock.close()
495
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400496 # AVB: if enabled, calculate and add hash to boot.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800497 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700498 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
499 part_size = info_dict["boot_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400500 cmd = [avbtool, "add_hash_footer", "--image", img.name,
501 "--partition_size", str(part_size), "--partition_name", "boot"]
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800502 AppendAVBSigningArgs(cmd, "boot")
503 args = info_dict.get("avb_boot_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400504 if args and args.strip():
505 cmd.extend(shlex.split(args))
506 p = Run(cmd, stdout=subprocess.PIPE)
507 p.communicate()
508 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
509 os.path.basename(OPTIONS.input_tmp))
David Zeuthend995f4b2016-01-29 16:59:17 -0500510
511 img.seek(os.SEEK_SET, 0)
512 data = img.read()
513
514 if has_ramdisk:
515 ramdisk_img.close()
516 img.close()
517
518 return data
519
520
Doug Zongkerd5131602012-08-02 14:46:42 -0700521def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800522 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700523 """Return a File object with the desired bootable image.
524
525 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
526 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
527 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700528
Doug Zongker55d93282011-01-25 17:03:34 -0800529 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
530 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800531 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800532 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700533
534 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
535 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800536 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700537 return File.FromLocalFile(name, prebuilt_path)
538
Tao Bao89fbb0f2017-01-10 10:47:58 -0800539 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700540
541 if info_dict is None:
542 info_dict = OPTIONS.info_dict
543
544 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800545 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
546 # for recovery.
547 has_ramdisk = (info_dict.get("system_root_image") != "true" or
548 prebuilt_name != "boot.img" or
549 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700550
Doug Zongker6f1d0312014-08-22 08:07:12 -0700551 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400552 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
553 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800554 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700555 if data:
556 return File(name, data)
557 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800558
Doug Zongkereef39442009-04-02 12:14:19 -0700559
Doug Zongker75f17362009-12-08 13:46:44 -0800560def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800561 """Unzip the given archive into a temporary directory and return the name.
562
563 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
564 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
565
566 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
567 main file), open for reading.
568 """
Doug Zongkereef39442009-04-02 12:14:19 -0700569
570 tmp = tempfile.mkdtemp(prefix="targetfiles-")
571 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800572
573 def unzip_to_dir(filename, dirname):
574 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
575 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800576 cmd.extend(pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800577 p = Run(cmd, stdout=subprocess.PIPE)
578 p.communicate()
579 if p.returncode != 0:
580 raise ExternalError("failed to unzip input target-files \"%s\"" %
581 (filename,))
582
583 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
584 if m:
585 unzip_to_dir(m.group(1), tmp)
586 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
587 filename = m.group(1)
588 else:
589 unzip_to_dir(filename, tmp)
590
591 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700592
593
594def GetKeyPasswords(keylist):
595 """Given a list of keys, prompt the user to enter passwords for
596 those which require them. Return a {key: password} dict. password
597 will be None if the key has no password."""
598
Doug Zongker8ce7c252009-05-22 13:34:54 -0700599 no_passwords = []
600 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700601 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700602 devnull = open("/dev/null", "w+b")
603 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800604 # We don't need a password for things that aren't really keys.
605 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700606 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700607 continue
608
T.R. Fullhart37e10522013-03-18 10:31:26 -0700609 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700610 "-inform", "DER", "-nocrypt"],
611 stdin=devnull.fileno(),
612 stdout=devnull.fileno(),
613 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700614 p.communicate()
615 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700616 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700617 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700618 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700619 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
620 "-inform", "DER", "-passin", "pass:"],
621 stdin=devnull.fileno(),
622 stdout=devnull.fileno(),
623 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700624 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700625 if p.returncode == 0:
626 # Encrypted key with empty string as password.
627 key_passwords[k] = ''
628 elif stderr.startswith('Error decrypting key'):
629 # Definitely encrypted key.
630 # It would have said "Error reading key" if it didn't parse correctly.
631 need_passwords.append(k)
632 else:
633 # Potentially, a type of key that openssl doesn't understand.
634 # We'll let the routines in signapk.jar handle it.
635 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700636 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700637
T.R. Fullhart37e10522013-03-18 10:31:26 -0700638 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700639 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700640 return key_passwords
641
642
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800643def GetMinSdkVersion(apk_name):
644 """Get the minSdkVersion delared in the APK. This can be both a decimal number
645 (API Level) or a codename.
646 """
647
648 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
649 output, err = p.communicate()
650 if err:
651 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
652 % (p.returncode,))
653
654 for line in output.split("\n"):
655 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
656 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
657 if m:
658 return m.group(1)
659 raise ExternalError("No minSdkVersion returned by aapt")
660
661
662def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
663 """Get the minSdkVersion declared in the APK as a number (API Level). If
664 minSdkVersion is set to a codename, it is translated to a number using the
665 provided map.
666 """
667
668 version = GetMinSdkVersion(apk_name)
669 try:
670 return int(version)
671 except ValueError:
672 # Not a decimal number. Codename?
673 if version in codename_to_api_level_map:
674 return codename_to_api_level_map[version]
675 else:
676 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
677 % (version, codename_to_api_level_map))
678
679
680def SignFile(input_name, output_name, key, password, min_api_level=None,
681 codename_to_api_level_map=dict(),
682 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700683 """Sign the input_name zip/jar/apk, producing output_name. Use the
684 given key and password (the latter may be None if the key does not
685 have a password.
686
Doug Zongker951495f2009-08-14 12:44:19 -0700687 If whole_file is true, use the "-w" option to SignApk to embed a
688 signature that covers the whole file in the archive comment of the
689 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800690
691 min_api_level is the API Level (int) of the oldest platform this file may end
692 up on. If not specified for an APK, the API Level is obtained by interpreting
693 the minSdkVersion attribute of the APK's AndroidManifest.xml.
694
695 codename_to_api_level_map is needed to translate the codename which may be
696 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700697 """
Doug Zongker951495f2009-08-14 12:44:19 -0700698
Alex Klyubin9667b182015-12-10 13:38:50 -0800699 java_library_path = os.path.join(
700 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
701
Tao Baoe95540e2016-11-08 12:08:53 -0800702 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
703 ["-Djava.library.path=" + java_library_path,
704 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
705 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700706 if whole_file:
707 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800708
709 min_sdk_version = min_api_level
710 if min_sdk_version is None:
711 if not whole_file:
712 min_sdk_version = GetMinSdkVersionInt(
713 input_name, codename_to_api_level_map)
714 if min_sdk_version is not None:
715 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
716
T.R. Fullhart37e10522013-03-18 10:31:26 -0700717 cmd.extend([key + OPTIONS.public_key_suffix,
718 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800719 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700720
721 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700722 if password is not None:
723 password += "\n"
724 p.communicate(password)
725 if p.returncode != 0:
726 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
727
Doug Zongkereef39442009-04-02 12:14:19 -0700728
Doug Zongker37974732010-09-16 17:44:38 -0700729def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700730 """Check the data string passed against the max size limit, if
731 any, for the given target. Raise exception if the data is too big.
732 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700733
Dan Albert8b72aef2015-03-23 19:13:21 -0700734 if target.endswith(".img"):
735 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700736 mount_point = "/" + target
737
Ying Wangf8824af2014-06-03 14:07:27 -0700738 fs_type = None
739 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700740 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700741 if mount_point == "/userdata":
742 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700743 p = info_dict["fstab"][mount_point]
744 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800745 device = p.device
746 if "/" in device:
747 device = device[device.rfind("/")+1:]
748 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700749 if not fs_type or not limit:
750 return
Doug Zongkereef39442009-04-02 12:14:19 -0700751
Andrew Boie0f9aec82012-02-14 09:32:52 -0800752 size = len(data)
753 pct = float(size) * 100.0 / limit
754 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
755 if pct >= 99.0:
756 raise ExternalError(msg)
757 elif pct >= 95.0:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800758 print("\n WARNING: %s\n" % (msg,))
Andrew Boie0f9aec82012-02-14 09:32:52 -0800759 elif OPTIONS.verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800760 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700761
762
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800763def ReadApkCerts(tf_zip):
764 """Given a target_files ZipFile, parse the META/apkcerts.txt file
765 and return a {package: cert} dict."""
766 certmap = {}
767 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
768 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700769 if not line:
770 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800771 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
772 r'private_key="(.*)"$', line)
773 if m:
774 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700775 public_key_suffix_len = len(OPTIONS.public_key_suffix)
776 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800777 if cert in SPECIAL_CERT_STRINGS and not privkey:
778 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700779 elif (cert.endswith(OPTIONS.public_key_suffix) and
780 privkey.endswith(OPTIONS.private_key_suffix) and
781 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
782 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800783 else:
784 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
785 return certmap
786
787
Doug Zongkereef39442009-04-02 12:14:19 -0700788COMMON_DOCSTRING = """
789 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700790 Prepend <dir>/bin to the list of places to search for binaries
791 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700792
Doug Zongker05d3dea2009-06-22 11:32:31 -0700793 -s (--device_specific) <file>
794 Path to the python module containing device-specific
795 releasetools code.
796
Doug Zongker8bec09e2009-11-30 15:37:14 -0800797 -x (--extra) <key=value>
798 Add a key/value pair to the 'extras' dict, which device-specific
799 extension code may look at.
800
Doug Zongkereef39442009-04-02 12:14:19 -0700801 -v (--verbose)
802 Show command lines being executed.
803
804 -h (--help)
805 Display this usage message and exit.
806"""
807
808def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800809 print(docstring.rstrip("\n"))
810 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700811
812
813def ParseOptions(argv,
814 docstring,
815 extra_opts="", extra_long_opts=(),
816 extra_option_handler=None):
817 """Parse the options in argv and return any arguments that aren't
818 flags. docstring is the calling module's docstring, to be displayed
819 for errors and -h. extra_opts and extra_long_opts are for flags
820 defined by the caller, which are processed by passing them to
821 extra_option_handler."""
822
823 try:
824 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800825 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800826 ["help", "verbose", "path=", "signapk_path=",
827 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700828 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700829 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
830 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800831 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700832 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700833 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700834 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800835 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700836 sys.exit(2)
837
Doug Zongkereef39442009-04-02 12:14:19 -0700838 for o, a in opts:
839 if o in ("-h", "--help"):
840 Usage(docstring)
841 sys.exit()
842 elif o in ("-v", "--verbose"):
843 OPTIONS.verbose = True
844 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700845 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700846 elif o in ("--signapk_path",):
847 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800848 elif o in ("--signapk_shared_library_path",):
849 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700850 elif o in ("--extra_signapk_args",):
851 OPTIONS.extra_signapk_args = shlex.split(a)
852 elif o in ("--java_path",):
853 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700854 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800855 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700856 elif o in ("--public_key_suffix",):
857 OPTIONS.public_key_suffix = a
858 elif o in ("--private_key_suffix",):
859 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800860 elif o in ("--boot_signer_path",):
861 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700862 elif o in ("--boot_signer_args",):
863 OPTIONS.boot_signer_args = shlex.split(a)
864 elif o in ("--verity_signer_path",):
865 OPTIONS.verity_signer_path = a
866 elif o in ("--verity_signer_args",):
867 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700868 elif o in ("-s", "--device_specific"):
869 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800870 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800871 key, value = a.split("=", 1)
872 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700873 else:
874 if extra_option_handler is None or not extra_option_handler(o, a):
875 assert False, "unknown option \"%s\"" % (o,)
876
Doug Zongker85448772014-09-09 14:59:20 -0700877 if OPTIONS.search_path:
878 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
879 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700880
881 return args
882
883
Tao Bao4c851b12016-09-19 13:54:38 -0700884def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700885 """Make a temp file and add it to the list of things to be deleted
886 when Cleanup() is called. Return the filename."""
887 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
888 os.close(fd)
889 OPTIONS.tempfiles.append(fn)
890 return fn
891
892
Doug Zongkereef39442009-04-02 12:14:19 -0700893def Cleanup():
894 for i in OPTIONS.tempfiles:
895 if os.path.isdir(i):
896 shutil.rmtree(i)
897 else:
898 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700899
900
901class PasswordManager(object):
902 def __init__(self):
903 self.editor = os.getenv("EDITOR", None)
904 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
905
906 def GetPasswords(self, items):
907 """Get passwords corresponding to each string in 'items',
908 returning a dict. (The dict may have keys in addition to the
909 values in 'items'.)
910
911 Uses the passwords in $ANDROID_PW_FILE if available, letting the
912 user edit that file to add more needed passwords. If no editor is
913 available, or $ANDROID_PW_FILE isn't define, prompts the user
914 interactively in the ordinary way.
915 """
916
917 current = self.ReadFile()
918
919 first = True
920 while True:
921 missing = []
922 for i in items:
923 if i not in current or not current[i]:
924 missing.append(i)
925 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700926 if not missing:
927 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700928
929 for i in missing:
930 current[i] = ""
931
932 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800933 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700934 answer = raw_input("try to edit again? [y]> ").strip()
935 if answer and answer[0] not in 'yY':
936 raise RuntimeError("key passwords unavailable")
937 first = False
938
939 current = self.UpdateAndReadFile(current)
940
Dan Albert8b72aef2015-03-23 19:13:21 -0700941 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700942 """Prompt the user to enter a value (password) for each key in
943 'current' whose value is fales. Returns a new dict with all the
944 values.
945 """
946 result = {}
947 for k, v in sorted(current.iteritems()):
948 if v:
949 result[k] = v
950 else:
951 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700952 result[k] = getpass.getpass(
953 "Enter password for %s key> " % k).strip()
954 if result[k]:
955 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700956 return result
957
958 def UpdateAndReadFile(self, current):
959 if not self.editor or not self.pwfile:
960 return self.PromptResult(current)
961
962 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700963 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700964 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
965 f.write("# (Additional spaces are harmless.)\n\n")
966
967 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700968 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
969 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700970 f.write("[[[ %s ]]] %s\n" % (v, k))
971 if not v and first_line is None:
972 # position cursor on first line with no password.
973 first_line = i + 4
974 f.close()
975
976 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
977 _, _ = p.communicate()
978
979 return self.ReadFile()
980
981 def ReadFile(self):
982 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700983 if self.pwfile is None:
984 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700985 try:
986 f = open(self.pwfile, "r")
987 for line in f:
988 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700989 if not line or line[0] == '#':
990 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700991 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
992 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800993 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700994 else:
995 result[m.group(2)] = m.group(1)
996 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700997 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700998 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800999 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001000 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001001
1002
Dan Albert8e0178d2015-01-27 15:53:15 -08001003def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1004 compress_type=None):
1005 import datetime
1006
1007 # http://b/18015246
1008 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1009 # for files larger than 2GiB. We can work around this by adjusting their
1010 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1011 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1012 # it isn't clear to me exactly what circumstances cause this).
1013 # `zipfile.write()` must be used directly to work around this.
1014 #
1015 # This mess can be avoided if we port to python3.
1016 saved_zip64_limit = zipfile.ZIP64_LIMIT
1017 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1018
1019 if compress_type is None:
1020 compress_type = zip_file.compression
1021 if arcname is None:
1022 arcname = filename
1023
1024 saved_stat = os.stat(filename)
1025
1026 try:
1027 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1028 # file to be zipped and reset it when we're done.
1029 os.chmod(filename, perms)
1030
1031 # Use a fixed timestamp so the output is repeatable.
1032 epoch = datetime.datetime.fromtimestamp(0)
1033 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1034 os.utime(filename, (timestamp, timestamp))
1035
1036 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1037 finally:
1038 os.chmod(filename, saved_stat.st_mode)
1039 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1040 zipfile.ZIP64_LIMIT = saved_zip64_limit
1041
1042
Tao Bao58c1b962015-05-20 09:32:18 -07001043def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001044 compress_type=None):
1045 """Wrap zipfile.writestr() function to work around the zip64 limit.
1046
1047 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1048 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1049 when calling crc32(bytes).
1050
1051 But it still works fine to write a shorter string into a large zip file.
1052 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1053 when we know the string won't be too long.
1054 """
1055
1056 saved_zip64_limit = zipfile.ZIP64_LIMIT
1057 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1058
1059 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1060 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001061 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001062 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001063 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001064 else:
Tao Baof3282b42015-04-01 11:21:55 -07001065 zinfo = zinfo_or_arcname
1066
1067 # If compress_type is given, it overrides the value in zinfo.
1068 if compress_type is not None:
1069 zinfo.compress_type = compress_type
1070
Tao Bao58c1b962015-05-20 09:32:18 -07001071 # If perms is given, it has a priority.
1072 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001073 # If perms doesn't set the file type, mark it as a regular file.
1074 if perms & 0o770000 == 0:
1075 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001076 zinfo.external_attr = perms << 16
1077
Tao Baof3282b42015-04-01 11:21:55 -07001078 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001079 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1080
Dan Albert8b72aef2015-03-23 19:13:21 -07001081 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001082 zipfile.ZIP64_LIMIT = saved_zip64_limit
1083
1084
1085def ZipClose(zip_file):
1086 # http://b/18015246
1087 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1088 # central directory.
1089 saved_zip64_limit = zipfile.ZIP64_LIMIT
1090 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1091
1092 zip_file.close()
1093
1094 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001095
1096
1097class DeviceSpecificParams(object):
1098 module = None
1099 def __init__(self, **kwargs):
1100 """Keyword arguments to the constructor become attributes of this
1101 object, which is passed to all functions in the device-specific
1102 module."""
1103 for k, v in kwargs.iteritems():
1104 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001105 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001106
1107 if self.module is None:
1108 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001109 if not path:
1110 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001111 try:
1112 if os.path.isdir(path):
1113 info = imp.find_module("releasetools", [path])
1114 else:
1115 d, f = os.path.split(path)
1116 b, x = os.path.splitext(f)
1117 if x == ".py":
1118 f = b
1119 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001120 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001121 self.module = imp.load_module("device_specific", *info)
1122 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001123 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001124
1125 def _DoCall(self, function_name, *args, **kwargs):
1126 """Call the named function in the device-specific module, passing
1127 the given args and kwargs. The first argument to the call will be
1128 the DeviceSpecific object itself. If there is no module, or the
1129 module does not define the function, return the value of the
1130 'default' kwarg (which itself defaults to None)."""
1131 if self.module is None or not hasattr(self.module, function_name):
1132 return kwargs.get("default", None)
1133 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1134
1135 def FullOTA_Assertions(self):
1136 """Called after emitting the block of assertions at the top of a
1137 full OTA package. Implementations can add whatever additional
1138 assertions they like."""
1139 return self._DoCall("FullOTA_Assertions")
1140
Doug Zongkere5ff5902012-01-17 10:55:37 -08001141 def FullOTA_InstallBegin(self):
1142 """Called at the start of full OTA installation."""
1143 return self._DoCall("FullOTA_InstallBegin")
1144
Doug Zongker05d3dea2009-06-22 11:32:31 -07001145 def FullOTA_InstallEnd(self):
1146 """Called at the end of full OTA installation; typically this is
1147 used to install the image for the device's baseband processor."""
1148 return self._DoCall("FullOTA_InstallEnd")
1149
1150 def IncrementalOTA_Assertions(self):
1151 """Called after emitting the block of assertions at the top of an
1152 incremental OTA package. Implementations can add whatever
1153 additional assertions they like."""
1154 return self._DoCall("IncrementalOTA_Assertions")
1155
Doug Zongkere5ff5902012-01-17 10:55:37 -08001156 def IncrementalOTA_VerifyBegin(self):
1157 """Called at the start of the verification phase of incremental
1158 OTA installation; additional checks can be placed here to abort
1159 the script before any changes are made."""
1160 return self._DoCall("IncrementalOTA_VerifyBegin")
1161
Doug Zongker05d3dea2009-06-22 11:32:31 -07001162 def IncrementalOTA_VerifyEnd(self):
1163 """Called at the end of the verification phase of incremental OTA
1164 installation; additional checks can be placed here to abort the
1165 script before any changes are made."""
1166 return self._DoCall("IncrementalOTA_VerifyEnd")
1167
Doug Zongkere5ff5902012-01-17 10:55:37 -08001168 def IncrementalOTA_InstallBegin(self):
1169 """Called at the start of incremental OTA installation (after
1170 verification is complete)."""
1171 return self._DoCall("IncrementalOTA_InstallBegin")
1172
Doug Zongker05d3dea2009-06-22 11:32:31 -07001173 def IncrementalOTA_InstallEnd(self):
1174 """Called at the end of incremental OTA installation; typically
1175 this is used to install the image for the device's baseband
1176 processor."""
1177 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001178
Tao Bao9bc6bb22015-11-09 16:58:28 -08001179 def VerifyOTA_Assertions(self):
1180 return self._DoCall("VerifyOTA_Assertions")
1181
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001182class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001183 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001184 self.name = name
1185 self.data = data
1186 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001187 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001188 self.sha1 = sha1(data).hexdigest()
1189
1190 @classmethod
1191 def FromLocalFile(cls, name, diskname):
1192 f = open(diskname, "rb")
1193 data = f.read()
1194 f.close()
1195 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001196
1197 def WriteToTemp(self):
1198 t = tempfile.NamedTemporaryFile()
1199 t.write(self.data)
1200 t.flush()
1201 return t
1202
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001203 def WriteToDir(self, d):
1204 with open(os.path.join(d, self.name), "wb") as fp:
1205 fp.write(self.data)
1206
Geremy Condra36bd3652014-02-06 19:45:10 -08001207 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001208 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001209
1210DIFF_PROGRAM_BY_EXT = {
1211 ".gz" : "imgdiff",
1212 ".zip" : ["imgdiff", "-z"],
1213 ".jar" : ["imgdiff", "-z"],
1214 ".apk" : ["imgdiff", "-z"],
1215 ".img" : "imgdiff",
1216 }
1217
1218class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001219 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001220 self.tf = tf
1221 self.sf = sf
1222 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001223 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001224
1225 def ComputePatch(self):
1226 """Compute the patch (as a string of data) needed to turn sf into
1227 tf. Returns the same tuple as GetPatch()."""
1228
1229 tf = self.tf
1230 sf = self.sf
1231
Doug Zongker24cd2802012-08-14 16:36:15 -07001232 if self.diff_program:
1233 diff_program = self.diff_program
1234 else:
1235 ext = os.path.splitext(tf.name)[1]
1236 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001237
1238 ttemp = tf.WriteToTemp()
1239 stemp = sf.WriteToTemp()
1240
1241 ext = os.path.splitext(tf.name)[1]
1242
1243 try:
1244 ptemp = tempfile.NamedTemporaryFile()
1245 if isinstance(diff_program, list):
1246 cmd = copy.copy(diff_program)
1247 else:
1248 cmd = [diff_program]
1249 cmd.append(stemp.name)
1250 cmd.append(ttemp.name)
1251 cmd.append(ptemp.name)
1252 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001253 err = []
1254 def run():
1255 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001256 if e:
1257 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001258 th = threading.Thread(target=run)
1259 th.start()
1260 th.join(timeout=300) # 5 mins
1261 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001262 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001263 p.terminate()
1264 th.join(5)
1265 if th.is_alive():
1266 p.kill()
1267 th.join()
1268
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001269 if err or p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001270 print("WARNING: failure running %s:\n%s\n" % (
1271 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001272 self.patch = None
1273 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001274 diff = ptemp.read()
1275 finally:
1276 ptemp.close()
1277 stemp.close()
1278 ttemp.close()
1279
1280 self.patch = diff
1281 return self.tf, self.sf, self.patch
1282
1283
1284 def GetPatch(self):
1285 """Return a tuple (target_file, source_file, patch_data).
1286 patch_data may be None if ComputePatch hasn't been called, or if
1287 computing the patch failed."""
1288 return self.tf, self.sf, self.patch
1289
1290
1291def ComputeDifferences(diffs):
1292 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001293 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001294
1295 # Do the largest files first, to try and reduce the long-pole effect.
1296 by_size = [(i.tf.size, i) for i in diffs]
1297 by_size.sort(reverse=True)
1298 by_size = [i[1] for i in by_size]
1299
1300 lock = threading.Lock()
1301 diff_iter = iter(by_size) # accessed under lock
1302
1303 def worker():
1304 try:
1305 lock.acquire()
1306 for d in diff_iter:
1307 lock.release()
1308 start = time.time()
1309 d.ComputePatch()
1310 dur = time.time() - start
1311 lock.acquire()
1312
1313 tf, sf, patch = d.GetPatch()
1314 if sf.name == tf.name:
1315 name = tf.name
1316 else:
1317 name = "%s (%s)" % (tf.name, sf.name)
1318 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001319 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001320 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001321 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1322 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001323 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001324 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001325 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001326 raise
1327
1328 # start worker threads; wait for them all to finish.
1329 threads = [threading.Thread(target=worker)
1330 for i in range(OPTIONS.worker_threads)]
1331 for th in threads:
1332 th.start()
1333 while threads:
1334 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001335
1336
Dan Albert8b72aef2015-03-23 19:13:21 -07001337class BlockDifference(object):
1338 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001339 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001340 self.tgt = tgt
1341 self.src = src
1342 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001343 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001344 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001345
Tao Baodd2a5892015-03-12 12:32:37 -07001346 if version is None:
1347 version = 1
1348 if OPTIONS.info_dict:
1349 version = max(
1350 int(i) for i in
1351 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001352 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001353 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001354
1355 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001356 version=self.version,
1357 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001358 tmpdir = tempfile.mkdtemp()
1359 OPTIONS.tempfiles.append(tmpdir)
1360 self.path = os.path.join(tmpdir, partition)
1361 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001362 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001363 self.touched_src_ranges = b.touched_src_ranges
1364 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001365
Tao Baoaac4ad52015-10-16 15:26:34 -07001366 if src is None:
1367 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1368 else:
1369 _, self.device = GetTypeAndDevice("/" + partition,
1370 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001371
Tao Baod8d14be2016-02-04 14:26:02 -08001372 @property
1373 def required_cache(self):
1374 return self._required_cache
1375
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001376 def WriteScript(self, script, output_zip, progress=None):
1377 if not self.src:
1378 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001379 script.Print("Patching %s image unconditionally..." % (self.partition,))
1380 else:
1381 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001382
Dan Albert8b72aef2015-03-23 19:13:21 -07001383 if progress:
1384 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001385 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001386 if OPTIONS.verify:
1387 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001388
Tao Bao9bc6bb22015-11-09 16:58:28 -08001389 def WriteStrictVerifyScript(self, script):
1390 """Verify all the blocks in the care_map, including clobbered blocks.
1391
1392 This differs from the WriteVerifyScript() function: a) it prints different
1393 error messages; b) it doesn't allow half-way updated images to pass the
1394 verification."""
1395
1396 partition = self.partition
1397 script.Print("Verifying %s..." % (partition,))
1398 ranges = self.tgt.care_map
1399 ranges_str = ranges.to_string_raw()
1400 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1401 'ui_print(" Verified.") || '
1402 'ui_print("\\"%s\\" has unexpected contents.");' % (
1403 self.device, ranges_str,
1404 self.tgt.TotalSha1(include_clobbered_blocks=True),
1405 self.device))
1406 script.AppendExtra("")
1407
Tao Baod522bdc2016-04-12 15:53:16 -07001408 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001409 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001410
1411 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001412 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001413 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001414
1415 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001416 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001417 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001418 ranges = self.touched_src_ranges
1419 expected_sha1 = self.touched_src_sha1
1420 else:
1421 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1422 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001423
1424 # No blocks to be checked, skipping.
1425 if not ranges:
1426 return
1427
Tao Bao5ece99d2015-05-12 11:42:31 -07001428 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001429 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1430 'block_image_verify("%s", '
1431 'package_extract_file("%s.transfer.list"), '
1432 '"%s.new.dat", "%s.patch.dat")) then') % (
1433 self.device, ranges_str, expected_sha1,
1434 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001435 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001436 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001437
Tianjie Xufc3422a2015-12-15 11:53:59 -08001438 if self.version >= 4:
1439
1440 # Bug: 21124327
1441 # When generating incrementals for the system and vendor partitions in
1442 # version 4 or newer, explicitly check the first block (which contains
1443 # the superblock) of the partition to see if it's what we expect. If
1444 # this check fails, give an explicit log message about the partition
1445 # having been remounted R/W (the most likely explanation).
1446 if self.check_first_block:
1447 script.AppendExtra('check_first_block("%s");' % (self.device,))
1448
1449 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001450 if partition == "system":
1451 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1452 else:
1453 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001454 script.AppendExtra((
1455 'ifelse (block_image_recover("{device}", "{ranges}") && '
1456 'block_image_verify("{device}", '
1457 'package_extract_file("{partition}.transfer.list"), '
1458 '"{partition}.new.dat", "{partition}.patch.dat"), '
1459 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001460 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001461 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001462 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001463
Tao Baodd2a5892015-03-12 12:32:37 -07001464 # Abort the OTA update. Note that the incremental OTA cannot be applied
1465 # even if it may match the checksum of the target partition.
1466 # a) If version < 3, operations like move and erase will make changes
1467 # unconditionally and damage the partition.
1468 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001469 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001470 if partition == "system":
1471 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1472 else:
1473 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1474 script.AppendExtra((
1475 'abort("E%d: %s partition has unexpected contents");\n'
1476 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001477
Tao Bao5fcaaef2015-06-01 13:40:49 -07001478 def _WritePostInstallVerifyScript(self, script):
1479 partition = self.partition
1480 script.Print('Verifying the updated %s image...' % (partition,))
1481 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1482 ranges = self.tgt.care_map
1483 ranges_str = ranges.to_string_raw()
1484 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1485 self.device, ranges_str,
1486 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001487
1488 # Bug: 20881595
1489 # Verify that extended blocks are really zeroed out.
1490 if self.tgt.extended:
1491 ranges_str = self.tgt.extended.to_string_raw()
1492 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1493 self.device, ranges_str,
1494 self._HashZeroBlocks(self.tgt.extended.size())))
1495 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001496 if partition == "system":
1497 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1498 else:
1499 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001500 script.AppendExtra(
1501 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001502 ' abort("E%d: %s partition has unexpected non-zero contents after '
1503 'OTA update");\n'
1504 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001505 else:
1506 script.Print('Verified the updated %s image.' % (partition,))
1507
Tianjie Xu209db462016-05-24 17:34:52 -07001508 if partition == "system":
1509 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1510 else:
1511 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1512
Tao Bao5fcaaef2015-06-01 13:40:49 -07001513 script.AppendExtra(
1514 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001515 ' abort("E%d: %s partition has unexpected contents after OTA '
1516 'update");\n'
1517 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001518
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001519 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001520 ZipWrite(output_zip,
1521 '{}.transfer.list'.format(self.path),
1522 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001523
1524 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1525 # almost triples the compression time but doesn't further reduce the size too much.
1526 # For a typical 1.8G system.new.dat
1527 # zip | brotli(quality 6) | brotli(quality 9)
1528 # compressed_size: 942M | 869M (~8% reduced) | 854M
1529 # compression_time: 75s | 265s | 719s
1530 # decompression_time: 15s | 25s | 25s
1531
1532 if not self.src:
1533 bro_cmd = ['bro', '--quality', '6',
1534 '--input', '{}.new.dat'.format(self.path),
1535 '--output', '{}.new.dat.br'.format(self.path)]
1536 print("Compressing {}.new.dat with brotli".format(self.partition))
1537 p = Run(bro_cmd, stdout=subprocess.PIPE)
1538 p.communicate()
1539 assert p.returncode == 0,\
1540 'compression of {}.new.dat failed'.format(self.partition)
1541
1542 new_data_name = '{}.new.dat.br'.format(self.partition)
1543 ZipWrite(output_zip,
1544 '{}.new.dat.br'.format(self.path),
1545 new_data_name,
1546 compress_type=zipfile.ZIP_STORED)
1547 else:
1548 new_data_name = '{}.new.dat'.format(self.partition)
1549 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1550
Dan Albert8e0178d2015-01-27 15:53:15 -08001551 ZipWrite(output_zip,
1552 '{}.patch.dat'.format(self.path),
1553 '{}.patch.dat'.format(self.partition),
1554 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001555
Tianjie Xu209db462016-05-24 17:34:52 -07001556 if self.partition == "system":
1557 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1558 else:
1559 code = ErrorCode.VENDOR_UPDATE_FAILURE
1560
Dan Albert8e0178d2015-01-27 15:53:15 -08001561 call = ('block_image_update("{device}", '
1562 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001563 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001564 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001565 device=self.device, partition=self.partition,
1566 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001567 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001568
Dan Albert8b72aef2015-03-23 19:13:21 -07001569 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001570 data = source.ReadRangeSet(ranges)
1571 ctx = sha1()
1572
1573 for p in data:
1574 ctx.update(p)
1575
1576 return ctx.hexdigest()
1577
Tao Baoe9b61912015-07-09 17:37:49 -07001578 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1579 """Return the hash value for all zero blocks."""
1580 zero_block = '\x00' * 4096
1581 ctx = sha1()
1582 for _ in range(num_blocks):
1583 ctx.update(zero_block)
1584
1585 return ctx.hexdigest()
1586
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001587
1588DataImage = blockimgdiff.DataImage
1589
Doug Zongker96a57e72010-09-26 14:57:41 -07001590# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001591PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001592 "ext4": "EMMC",
1593 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001594 "f2fs": "EMMC",
1595 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001596}
Doug Zongker96a57e72010-09-26 14:57:41 -07001597
1598def GetTypeAndDevice(mount_point, info):
1599 fstab = info["fstab"]
1600 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001601 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1602 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001603 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001604 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001605
1606
1607def ParseCertificate(data):
1608 """Parse a PEM-format certificate."""
1609 cert = []
1610 save = False
1611 for line in data.split("\n"):
1612 if "--END CERTIFICATE--" in line:
1613 break
1614 if save:
1615 cert.append(line)
1616 if "--BEGIN CERTIFICATE--" in line:
1617 save = True
1618 cert = "".join(cert).decode('base64')
1619 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001620
Doug Zongker412c02f2014-02-13 10:58:24 -08001621def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1622 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001623 """Generate a binary patch that creates the recovery image starting
1624 with the boot image. (Most of the space in these images is just the
1625 kernel, which is identical for the two, so the resulting patch
1626 should be efficient.) Add it to the output zip, along with a shell
1627 script that is run from init.rc on first boot to actually do the
1628 patching and install the new recovery image.
1629
1630 recovery_img and boot_img should be File objects for the
1631 corresponding images. info should be the dictionary returned by
1632 common.LoadInfoDict() on the input target_files.
1633 """
1634
Doug Zongker412c02f2014-02-13 10:58:24 -08001635 if info_dict is None:
1636 info_dict = OPTIONS.info_dict
1637
Tao Baof2cffbd2015-07-22 12:33:18 -07001638 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001639
Tao Baof2cffbd2015-07-22 12:33:18 -07001640 if full_recovery_image:
1641 output_sink("etc/recovery.img", recovery_img.data)
1642
1643 else:
1644 diff_program = ["imgdiff"]
1645 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1646 if os.path.exists(path):
1647 diff_program.append("-b")
1648 diff_program.append(path)
1649 bonus_args = "-b /system/etc/recovery-resource.dat"
1650 else:
1651 bonus_args = ""
1652
1653 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1654 _, _, patch = d.ComputePatch()
1655 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001656
Dan Albertebb19aa2015-03-27 19:11:53 -07001657 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001658 # The following GetTypeAndDevice()s need to use the path in the target
1659 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001660 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1661 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1662 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001663 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001664
Tao Baof2cffbd2015-07-22 12:33:18 -07001665 if full_recovery_image:
1666 sh = """#!/system/bin/sh
1667if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1668 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"
1669else
1670 log -t recovery "Recovery image already installed"
1671fi
1672""" % {'type': recovery_type,
1673 'device': recovery_device,
1674 'sha1': recovery_img.sha1,
1675 'size': recovery_img.size}
1676 else:
1677 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001678if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1679 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"
1680else
1681 log -t recovery "Recovery image already installed"
1682fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001683""" % {'boot_size': boot_img.size,
1684 'boot_sha1': boot_img.sha1,
1685 'recovery_size': recovery_img.size,
1686 'recovery_sha1': recovery_img.sha1,
1687 'boot_type': boot_type,
1688 'boot_device': boot_device,
1689 'recovery_type': recovery_type,
1690 'recovery_device': recovery_device,
1691 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001692
1693 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001694 # in the L release.
1695 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001696
Tao Bao89fbb0f2017-01-10 10:47:58 -08001697 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001698
1699 output_sink(sh_location, sh)