blob: 370710ecafe230c7420a2541788e076a1ec4ded8 [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
Narayan Kamatha07bf042017-08-14 14:49:21 +010021import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070022import imp
Doug Zongkereef39442009-04-02 12:14:19 -070023import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080024import platform
Doug Zongkereef39442009-04-02 12:14:19 -070025import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070026import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070027import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080028import string
Doug Zongkereef39442009-04-02 12:14:19 -070029import subprocess
30import sys
31import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070032import threading
33import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070034import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080035from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070036
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070037import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080038import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070039
Dan Albert8b72aef2015-03-23 19:13:21 -070040class Options(object):
41 def __init__(self):
42 platform_search_path = {
43 "linux2": "out/host/linux-x86",
44 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070045 }
Doug Zongker85448772014-09-09 14:59:20 -070046
Dan Albert8b72aef2015-03-23 19:13:21 -070047 self.search_path = platform_search_path.get(sys.platform, None)
48 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080049 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070050 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080052 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070053 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070055 # use otatools built boot_signer by default
56 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070057 self.boot_signer_args = []
58 self.verity_signer_path = None
59 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.verbose = False
61 self.tempfiles = []
62 self.device_specific = None
63 self.extras = {}
64 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070065 self.source_info_dict = None
66 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070068 # Stash size cannot exceed cache_size * threshold.
69 self.cache_size = None
70 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070071
72
73OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070074
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080075
76# Values for "certificate" in apkcerts that mean special things.
77SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
78
Tao Bao9dd909e2017-11-14 11:27:32 -080079
80# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Jaekyun Seokb7735d82017-11-27 17:04:47 +090081AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product', 'dtbo')
Tao Bao9dd909e2017-11-14 11:27:32 -080082
83
Tianjie Xu209db462016-05-24 17:34:52 -070084class ErrorCode(object):
85 """Define error_codes for failures that happen during the actual
86 update package installation.
87
88 Error codes 0-999 are reserved for failures before the package
89 installation (i.e. low battery, package verification failure).
90 Detailed code in 'bootable/recovery/error_code.h' """
91
92 SYSTEM_VERIFICATION_FAILURE = 1000
93 SYSTEM_UPDATE_FAILURE = 1001
94 SYSTEM_UNEXPECTED_CONTENTS = 1002
95 SYSTEM_NONZERO_CONTENTS = 1003
96 SYSTEM_RECOVER_FAILURE = 1004
97 VENDOR_VERIFICATION_FAILURE = 2000
98 VENDOR_UPDATE_FAILURE = 2001
99 VENDOR_UNEXPECTED_CONTENTS = 2002
100 VENDOR_NONZERO_CONTENTS = 2003
101 VENDOR_RECOVER_FAILURE = 2004
102 OEM_PROP_MISMATCH = 3000
103 FINGERPRINT_MISMATCH = 3001
104 THUMBPRINT_MISMATCH = 3002
105 OLDER_BUILD = 3003
106 DEVICE_MISMATCH = 3004
107 BAD_PATCH_FILE = 3005
108 INSUFFICIENT_CACHE_SPACE = 3006
109 TUNE_PARTITION_FAILURE = 3007
110 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800111
Dan Albert8b72aef2015-03-23 19:13:21 -0700112class ExternalError(RuntimeError):
113 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700114
115
Tao Bao39451582017-05-04 11:10:47 -0700116def Run(args, verbose=None, **kwargs):
117 """Create and return a subprocess.Popen object.
118
119 Caller can specify if the command line should be printed. The global
120 OPTIONS.verbose will be used if not specified.
121 """
122 if verbose is None:
123 verbose = OPTIONS.verbose
124 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800125 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700126 return subprocess.Popen(args, **kwargs)
127
128
Tao Baoc765cca2018-01-31 17:32:40 -0800129def RoundUpTo4K(value):
130 rounded_up = value + 4095
131 return rounded_up - (rounded_up % 4096)
132
133
Ying Wang7e6d4e42010-12-13 16:25:36 -0800134def CloseInheritedPipes():
135 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
136 before doing other work."""
137 if platform.system() != "Darwin":
138 return
139 for d in range(3, 1025):
140 try:
141 stat = os.fstat(d)
142 if stat is not None:
143 pipebit = stat[0] & 0x1000
144 if pipebit != 0:
145 os.close(d)
146 except OSError:
147 pass
148
149
Tao Bao2c15d9e2015-07-09 11:51:16 -0700150def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700151 """Read and parse the META/misc_info.txt key/value pairs from the
152 input target files and return a dict."""
153
Doug Zongkerc9253822014-02-04 12:17:58 -0800154 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700155 if isinstance(input_file, zipfile.ZipFile):
156 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800157 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700158 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800159 try:
160 with open(path) as f:
161 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700162 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800163 if e.errno == errno.ENOENT:
164 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800165
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700166 try:
Michael Runge6e836112014-04-15 17:40:21 -0700167 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700168 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800169 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700170
Tao Bao6cd54732017-02-27 15:12:05 -0800171 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800172 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800173
Tao Bao84e75682015-07-19 02:38:53 -0700174 # A few properties are stored as links to the files in the out/ directory.
175 # It works fine with the build system. However, they are no longer available
176 # when (re)generating from target_files zip. If input_dir is not None, we
177 # are doing repacking. Redirect those properties to the actual files in the
178 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700179 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400180 # We carry a copy of file_contexts.bin under META/. If not available,
181 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700182 # to build images than the one running on device, such as when enabling
183 # system_root_image. In that case, we must have the one for image
184 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700185 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
186 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700187 if d.get("system_root_image") == "true":
188 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700189 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700190 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700191 if not os.path.exists(fc_config):
192 fc_config = None
193
194 if fc_config:
195 d["selinux_fc"] = fc_config
196
Tao Bao84e75682015-07-19 02:38:53 -0700197 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
198 if d.get("system_root_image") == "true":
199 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
200 d["ramdisk_fs_config"] = os.path.join(
201 input_dir, "META", "root_filesystem_config.txt")
202
Tao Baof54216f2016-03-29 15:12:37 -0700203 # Redirect {system,vendor}_base_fs_file.
204 if "system_base_fs_file" in d:
205 basename = os.path.basename(d["system_base_fs_file"])
206 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700207 if os.path.exists(system_base_fs_file):
208 d["system_base_fs_file"] = system_base_fs_file
209 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800210 print("Warning: failed to find system base fs file: %s" % (
211 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700212 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700213
214 if "vendor_base_fs_file" in d:
215 basename = os.path.basename(d["vendor_base_fs_file"])
216 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700217 if os.path.exists(vendor_base_fs_file):
218 d["vendor_base_fs_file"] = vendor_base_fs_file
219 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800220 print("Warning: failed to find vendor base fs file: %s" % (
221 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700222 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700223
Doug Zongker37974732010-09-16 17:44:38 -0700224 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800225 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700226 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700227 if not line:
228 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700229 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700230 if not value:
231 continue
Doug Zongker37974732010-09-16 17:44:38 -0700232 if name == "blocksize":
233 d[name] = value
234 else:
235 d[name + "_size"] = value
236 except KeyError:
237 pass
238
239 def makeint(key):
240 if key in d:
241 d[key] = int(d[key], 0)
242
243 makeint("recovery_api_version")
244 makeint("blocksize")
245 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700246 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700247 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700248 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700249 makeint("recovery_size")
250 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800251 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700252
Tianjie Xucfa86222016-03-07 16:31:19 -0800253 system_root_image = d.get("system_root_image", None) == "true"
254 if d.get("no_recovery", None) != "true":
255 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800256 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800257 recovery_fstab_path, system_root_image)
258 elif d.get("recovery_as_boot", None) == "true":
259 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
260 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
261 recovery_fstab_path, system_root_image)
262 else:
263 d["fstab"] = None
264
Tao Baobcd1d162017-08-26 13:10:26 -0700265 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
266 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Tao Bao12d87fc2018-01-31 12:18:52 -0800267
268 # Set up the salt (based on fingerprint or thumbprint) that will be used when
269 # adding AVB footer.
270 if d.get("avb_enable") == "true":
271 fp = None
272 if "build.prop" in d:
273 build_prop = d["build.prop"]
274 if "ro.build.fingerprint" in build_prop:
275 fp = build_prop["ro.build.fingerprint"]
276 elif "ro.build.thumbprint" in build_prop:
277 fp = build_prop["ro.build.thumbprint"]
278 if fp:
279 d["avb_salt"] = sha256(fp).hexdigest()
280
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700281 return d
282
Tao Baod1de6f32017-03-01 16:38:48 -0800283
Tao Baobcd1d162017-08-26 13:10:26 -0700284def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700285 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700286 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700287 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700288 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700289 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700290 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700291
Tao Baod1de6f32017-03-01 16:38:48 -0800292
Michael Runge6e836112014-04-15 17:40:21 -0700293def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700294 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700295 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700296 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700297 if not line or line.startswith("#"):
298 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700299 if "=" in line:
300 name, value = line.split("=", 1)
301 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700302 return d
303
Tao Baod1de6f32017-03-01 16:38:48 -0800304
Tianjie Xucfa86222016-03-07 16:31:19 -0800305def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
306 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700307 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800308 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700309 self.mount_point = mount_point
310 self.fs_type = fs_type
311 self.device = device
312 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700313 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700314
315 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800316 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700317 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800318 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700319 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700320
Tao Baod1de6f32017-03-01 16:38:48 -0800321 assert fstab_version == 2
322
323 d = {}
324 for line in data.split("\n"):
325 line = line.strip()
326 if not line or line.startswith("#"):
327 continue
328
329 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
330 pieces = line.split()
331 if len(pieces) != 5:
332 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
333
334 # Ignore entries that are managed by vold.
335 options = pieces[4]
336 if "voldmanaged=" in options:
337 continue
338
339 # It's a good line, parse it.
340 length = 0
341 options = options.split(",")
342 for i in options:
343 if i.startswith("length="):
344 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800345 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800346 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700347 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800348
Tao Baod1de6f32017-03-01 16:38:48 -0800349 mount_flags = pieces[3]
350 # Honor the SELinux context if present.
351 context = None
352 for i in mount_flags.split(","):
353 if i.startswith("context="):
354 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800355
Tao Baod1de6f32017-03-01 16:38:48 -0800356 mount_point = pieces[1]
357 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
358 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800359
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700360 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700361 # system. Other areas assume system is always at "/system" so point /system
362 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700363 if system_root_image:
364 assert not d.has_key("/system") and d.has_key("/")
365 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700366 return d
367
368
Doug Zongker37974732010-09-16 17:44:38 -0700369def DumpInfoDict(d):
370 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800371 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700372
Dan Albert8b72aef2015-03-23 19:13:21 -0700373
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800374def AppendAVBSigningArgs(cmd, partition):
375 """Append signing arguments for avbtool."""
376 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
377 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
378 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
379 if key_path and algorithm:
380 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700381 avb_salt = OPTIONS.info_dict.get("avb_salt")
382 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
383 if avb_salt and partition != "vbmeta":
384 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800385
386
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700387def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800388 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700389 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700390
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700391 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800392 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
393 we are building a two-step special image (i.e. building a recovery image to
394 be loaded into /boot in two-step OTAs).
395
396 Return the image data, or None if sourcedir does not appear to contains files
397 for building the requested image.
398 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700399
400 def make_ramdisk():
401 ramdisk_img = tempfile.NamedTemporaryFile()
402
403 if os.access(fs_config_file, os.F_OK):
404 cmd = ["mkbootfs", "-f", fs_config_file,
405 os.path.join(sourcedir, "RAMDISK")]
406 else:
407 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
408 p1 = Run(cmd, stdout=subprocess.PIPE)
409 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
410
411 p2.wait()
412 p1.wait()
413 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
414 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
415
416 return ramdisk_img
417
418 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
419 return None
420
421 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700422 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700423
Doug Zongkerd5131602012-08-02 14:46:42 -0700424 if info_dict is None:
425 info_dict = OPTIONS.info_dict
426
Doug Zongkereef39442009-04-02 12:14:19 -0700427 img = tempfile.NamedTemporaryFile()
428
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700429 if has_ramdisk:
430 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700431
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800432 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
433 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
434
435 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700436
Benoit Fradina45a8682014-07-14 21:00:43 +0200437 fn = os.path.join(sourcedir, "second")
438 if os.access(fn, os.F_OK):
439 cmd.append("--second")
440 cmd.append(fn)
441
Doug Zongker171f1cd2009-06-15 22:36:37 -0700442 fn = os.path.join(sourcedir, "cmdline")
443 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700444 cmd.append("--cmdline")
445 cmd.append(open(fn).read().rstrip("\n"))
446
447 fn = os.path.join(sourcedir, "base")
448 if os.access(fn, os.F_OK):
449 cmd.append("--base")
450 cmd.append(open(fn).read().rstrip("\n"))
451
Ying Wang4de6b5b2010-08-25 14:29:34 -0700452 fn = os.path.join(sourcedir, "pagesize")
453 if os.access(fn, os.F_OK):
454 cmd.append("--pagesize")
455 cmd.append(open(fn).read().rstrip("\n"))
456
Doug Zongkerd5131602012-08-02 14:46:42 -0700457 args = info_dict.get("mkbootimg_args", None)
458 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700459 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700460
Sami Tolvanen3303d902016-03-15 16:49:30 +0000461 args = info_dict.get("mkbootimg_version_args", None)
462 if args and args.strip():
463 cmd.extend(shlex.split(args))
464
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700465 if has_ramdisk:
466 cmd.extend(["--ramdisk", ramdisk_img.name])
467
Tao Baod95e9fd2015-03-29 23:07:41 -0700468 img_unsigned = None
469 if info_dict.get("vboot", None):
470 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700471 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700472 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700473 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700474
Tao Baobf70c3182017-07-11 17:27:55 -0700475 # "boot" or "recovery", without extension.
476 partition_name = os.path.basename(sourcedir).lower()
477
Doug Zongker38a649f2009-06-17 09:07:09 -0700478 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700479 p.communicate()
Tao Baobf70c3182017-07-11 17:27:55 -0700480 assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
Doug Zongkereef39442009-04-02 12:14:19 -0700481
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100482 if (info_dict.get("boot_signer", None) == "true" and
483 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800484 # Hard-code the path as "/boot" for two-step special recovery image (which
485 # will be loaded into /boot during the two-step OTA).
486 if two_step_image:
487 path = "/boot"
488 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700489 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700490 cmd = [OPTIONS.boot_signer_path]
491 cmd.extend(OPTIONS.boot_signer_args)
492 cmd.extend([path, img.name,
493 info_dict["verity_key"] + ".pk8",
494 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700495 p = Run(cmd, stdout=subprocess.PIPE)
496 p.communicate()
497 assert p.returncode == 0, "boot_signer of %s image failed" % path
498
Tao Baod95e9fd2015-03-29 23:07:41 -0700499 # Sign the image if vboot is non-empty.
500 elif info_dict.get("vboot", None):
Tao Baobf70c3182017-07-11 17:27:55 -0700501 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700502 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800503 # We have switched from the prebuilt futility binary to using the tool
504 # (futility-host) built from the source. Override the setting in the old
505 # TF.zip.
506 futility = info_dict["futility"]
507 if futility.startswith("prebuilts/"):
508 futility = "futility-host"
509 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700510 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700511 info_dict["vboot_key"] + ".vbprivk",
512 info_dict["vboot_subkey"] + ".vbprivk",
513 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700514 img.name]
515 p = Run(cmd, stdout=subprocess.PIPE)
516 p.communicate()
517 assert p.returncode == 0, "vboot_signer of %s image failed" % path
518
Tao Baof3282b42015-04-01 11:21:55 -0700519 # Clean up the temp files.
520 img_unsigned.close()
521 img_keyblock.close()
522
David Zeuthen8fecb282017-12-01 16:24:01 -0500523 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800524 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700525 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500526 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400527 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700528 "--partition_size", str(part_size), "--partition_name",
529 partition_name]
530 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500531 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400532 if args and args.strip():
533 cmd.extend(shlex.split(args))
534 p = Run(cmd, stdout=subprocess.PIPE)
535 p.communicate()
536 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
Tao Baobf70c3182017-07-11 17:27:55 -0700537 partition_name,)
David Zeuthend995f4b2016-01-29 16:59:17 -0500538
539 img.seek(os.SEEK_SET, 0)
540 data = img.read()
541
542 if has_ramdisk:
543 ramdisk_img.close()
544 img.close()
545
546 return data
547
548
Doug Zongkerd5131602012-08-02 14:46:42 -0700549def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800550 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700551 """Return a File object with the desired bootable image.
552
553 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
554 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
555 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700556
Doug Zongker55d93282011-01-25 17:03:34 -0800557 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
558 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800559 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800560 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700561
562 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
563 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800564 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700565 return File.FromLocalFile(name, prebuilt_path)
566
Tao Bao89fbb0f2017-01-10 10:47:58 -0800567 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700568
569 if info_dict is None:
570 info_dict = OPTIONS.info_dict
571
572 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800573 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
574 # for recovery.
575 has_ramdisk = (info_dict.get("system_root_image") != "true" or
576 prebuilt_name != "boot.img" or
577 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700578
Doug Zongker6f1d0312014-08-22 08:07:12 -0700579 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400580 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
581 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800582 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700583 if data:
584 return File(name, data)
585 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800586
Doug Zongkereef39442009-04-02 12:14:19 -0700587
Narayan Kamatha07bf042017-08-14 14:49:21 +0100588def Gunzip(in_filename, out_filename):
589 """Gunzip the given gzip compressed file to a given output file.
590 """
591 with gzip.open(in_filename, "rb") as in_file, open(out_filename, "wb") as out_file:
592 shutil.copyfileobj(in_file, out_file)
593
594
Doug Zongker75f17362009-12-08 13:46:44 -0800595def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800596 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800597
Tao Bao1c830bf2017-12-25 10:43:47 -0800598 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
599 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800600
Tao Bao1c830bf2017-12-25 10:43:47 -0800601 Returns:
602 (tempdir, zipobj): tempdir is the name of the temprary directory; zipobj is
603 a zipfile.ZipFile (of the main file), open for reading.
Doug Zongker55d93282011-01-25 17:03:34 -0800604 """
Doug Zongkereef39442009-04-02 12:14:19 -0700605
Doug Zongker55d93282011-01-25 17:03:34 -0800606 def unzip_to_dir(filename, dirname):
607 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
608 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800609 cmd.extend(pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800610 p = Run(cmd, stdout=subprocess.PIPE)
611 p.communicate()
612 if p.returncode != 0:
613 raise ExternalError("failed to unzip input target-files \"%s\"" %
614 (filename,))
615
Tao Bao1c830bf2017-12-25 10:43:47 -0800616 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800617 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
618 if m:
619 unzip_to_dir(m.group(1), tmp)
620 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
621 filename = m.group(1)
622 else:
623 unzip_to_dir(filename, tmp)
624
625 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700626
627
Tao Baoe709b092018-02-07 12:40:00 -0800628def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -0800629 """Returns a SparseImage object suitable for passing to BlockImageDiff.
630
631 This function loads the specified sparse image from the given path, and
632 performs additional processing for OTA purpose. For example, it always adds
633 block 0 to clobbered blocks list. It also detects files that cannot be
634 reconstructed from the block list, for whom we should avoid applying imgdiff.
635
636 Args:
637 which: The partition name, which must be "system" or "vendor".
638 tmpdir: The directory that contains the prebuilt image and block map file.
639 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800640 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -0800641
642 Returns:
643 A SparseImage object, with file_map info loaded.
644 """
645 assert which in ("system", "vendor")
646
647 path = os.path.join(tmpdir, "IMAGES", which + ".img")
648 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
649
650 # The image and map files must have been created prior to calling
651 # ota_from_target_files.py (since LMP).
652 assert os.path.exists(path) and os.path.exists(mappath)
653
654 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
655 # it to clobbered_blocks so that it will be written to the target
656 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
657 clobbered_blocks = "0"
658
Tao Baoe709b092018-02-07 12:40:00 -0800659 image = sparse_img.SparseImage(path, mappath, clobbered_blocks,
660 allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -0800661
662 # block.map may contain less blocks, because mke2fs may skip allocating blocks
663 # if they contain all zeros. We can't reconstruct such a file from its block
664 # list. Tag such entries accordingly. (Bug: 65213616)
665 for entry in image.file_map:
666 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar".
667 arcname = string.replace(entry, which, which.upper(), 1)[1:]
668 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
669 if arcname not in input_zip.namelist():
670 continue
671
672 info = input_zip.getinfo(arcname)
673 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800674
675 # If a RangeSet has been tagged as using shared blocks while loading the
676 # image, its block list must be already incomplete due to that reason. Don't
677 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
678 if ranges.extra.get('uses_shared_blocks'):
679 continue
680
Tao Baoc765cca2018-01-31 17:32:40 -0800681 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
682 ranges.extra['incomplete'] = True
683
684 return image
685
686
Doug Zongkereef39442009-04-02 12:14:19 -0700687def GetKeyPasswords(keylist):
688 """Given a list of keys, prompt the user to enter passwords for
689 those which require them. Return a {key: password} dict. password
690 will be None if the key has no password."""
691
Doug Zongker8ce7c252009-05-22 13:34:54 -0700692 no_passwords = []
693 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700694 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700695 devnull = open("/dev/null", "w+b")
696 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800697 # We don't need a password for things that aren't really keys.
698 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700699 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700700 continue
701
T.R. Fullhart37e10522013-03-18 10:31:26 -0700702 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700703 "-inform", "DER", "-nocrypt"],
704 stdin=devnull.fileno(),
705 stdout=devnull.fileno(),
706 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700707 p.communicate()
708 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700709 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700710 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700711 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700712 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
713 "-inform", "DER", "-passin", "pass:"],
714 stdin=devnull.fileno(),
715 stdout=devnull.fileno(),
716 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700717 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700718 if p.returncode == 0:
719 # Encrypted key with empty string as password.
720 key_passwords[k] = ''
721 elif stderr.startswith('Error decrypting key'):
722 # Definitely encrypted key.
723 # It would have said "Error reading key" if it didn't parse correctly.
724 need_passwords.append(k)
725 else:
726 # Potentially, a type of key that openssl doesn't understand.
727 # We'll let the routines in signapk.jar handle it.
728 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700729 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700730
T.R. Fullhart37e10522013-03-18 10:31:26 -0700731 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700732 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700733 return key_passwords
734
735
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800736def GetMinSdkVersion(apk_name):
737 """Get the minSdkVersion delared in the APK. This can be both a decimal number
738 (API Level) or a codename.
739 """
740
741 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
742 output, err = p.communicate()
743 if err:
744 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
745 % (p.returncode,))
746
747 for line in output.split("\n"):
748 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
749 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
750 if m:
751 return m.group(1)
752 raise ExternalError("No minSdkVersion returned by aapt")
753
754
755def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
756 """Get the minSdkVersion declared in the APK as a number (API Level). If
757 minSdkVersion is set to a codename, it is translated to a number using the
758 provided map.
759 """
760
761 version = GetMinSdkVersion(apk_name)
762 try:
763 return int(version)
764 except ValueError:
765 # Not a decimal number. Codename?
766 if version in codename_to_api_level_map:
767 return codename_to_api_level_map[version]
768 else:
769 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
770 % (version, codename_to_api_level_map))
771
772
773def SignFile(input_name, output_name, key, password, min_api_level=None,
774 codename_to_api_level_map=dict(),
775 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700776 """Sign the input_name zip/jar/apk, producing output_name. Use the
777 given key and password (the latter may be None if the key does not
778 have a password.
779
Doug Zongker951495f2009-08-14 12:44:19 -0700780 If whole_file is true, use the "-w" option to SignApk to embed a
781 signature that covers the whole file in the archive comment of the
782 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800783
784 min_api_level is the API Level (int) of the oldest platform this file may end
785 up on. If not specified for an APK, the API Level is obtained by interpreting
786 the minSdkVersion attribute of the APK's AndroidManifest.xml.
787
788 codename_to_api_level_map is needed to translate the codename which may be
789 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700790 """
Doug Zongker951495f2009-08-14 12:44:19 -0700791
Alex Klyubin9667b182015-12-10 13:38:50 -0800792 java_library_path = os.path.join(
793 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
794
Tao Baoe95540e2016-11-08 12:08:53 -0800795 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
796 ["-Djava.library.path=" + java_library_path,
797 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
798 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700799 if whole_file:
800 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800801
802 min_sdk_version = min_api_level
803 if min_sdk_version is None:
804 if not whole_file:
805 min_sdk_version = GetMinSdkVersionInt(
806 input_name, codename_to_api_level_map)
807 if min_sdk_version is not None:
808 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
809
T.R. Fullhart37e10522013-03-18 10:31:26 -0700810 cmd.extend([key + OPTIONS.public_key_suffix,
811 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800812 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700813
814 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700815 if password is not None:
816 password += "\n"
817 p.communicate(password)
818 if p.returncode != 0:
819 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
820
Doug Zongkereef39442009-04-02 12:14:19 -0700821
Doug Zongker37974732010-09-16 17:44:38 -0700822def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800823 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700824
Tao Bao9dd909e2017-11-14 11:27:32 -0800825 For non-AVB images, raise exception if the data is too big. Print a warning
826 if the data is nearing the maximum size.
827
828 For AVB images, the actual image size should be identical to the limit.
829
830 Args:
831 data: A string that contains all the data for the partition.
832 target: The partition name. The ".img" suffix is optional.
833 info_dict: The dict to be looked up for relevant info.
834 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700835 if target.endswith(".img"):
836 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700837 mount_point = "/" + target
838
Ying Wangf8824af2014-06-03 14:07:27 -0700839 fs_type = None
840 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700841 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700842 if mount_point == "/userdata":
843 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700844 p = info_dict["fstab"][mount_point]
845 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800846 device = p.device
847 if "/" in device:
848 device = device[device.rfind("/")+1:]
849 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700850 if not fs_type or not limit:
851 return
Doug Zongkereef39442009-04-02 12:14:19 -0700852
Andrew Boie0f9aec82012-02-14 09:32:52 -0800853 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800854 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
855 # path.
856 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
857 if size != limit:
858 raise ExternalError(
859 "Mismatching image size for %s: expected %d actual %d" % (
860 target, limit, size))
861 else:
862 pct = float(size) * 100.0 / limit
863 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
864 if pct >= 99.0:
865 raise ExternalError(msg)
866 elif pct >= 95.0:
867 print("\n WARNING: %s\n" % (msg,))
868 elif OPTIONS.verbose:
869 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700870
871
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800872def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -0800873 """Parses the APK certs info from a given target-files zip.
874
875 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
876 tuple with the following elements: (1) a dictionary that maps packages to
877 certs (based on the "certificate" and "private_key" attributes in the file;
878 (2) a string representing the extension of compressed APKs in the target files
879 (e.g ".gz", ".bro").
880
881 Args:
882 tf_zip: The input target_files ZipFile (already open).
883
884 Returns:
885 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
886 the extension string of compressed APKs (e.g. ".gz"), or None if there's
887 no compressed APKs.
888 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800889 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +0100890 compressed_extension = None
891
Tao Bao0f990332017-09-08 19:02:54 -0700892 # META/apkcerts.txt contains the info for _all_ the packages known at build
893 # time. Filter out the ones that are not installed.
894 installed_files = set()
895 for name in tf_zip.namelist():
896 basename = os.path.basename(name)
897 if basename:
898 installed_files.add(basename)
899
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800900 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
901 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700902 if not line:
903 continue
Tao Bao818ddf52018-01-05 11:17:34 -0800904 m = re.match(
905 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
906 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
907 line)
908 if not m:
909 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +0100910
Tao Bao818ddf52018-01-05 11:17:34 -0800911 matches = m.groupdict()
912 cert = matches["CERT"]
913 privkey = matches["PRIVKEY"]
914 name = matches["NAME"]
915 this_compressed_extension = matches["COMPRESSED"]
916
917 public_key_suffix_len = len(OPTIONS.public_key_suffix)
918 private_key_suffix_len = len(OPTIONS.private_key_suffix)
919 if cert in SPECIAL_CERT_STRINGS and not privkey:
920 certmap[name] = cert
921 elif (cert.endswith(OPTIONS.public_key_suffix) and
922 privkey.endswith(OPTIONS.private_key_suffix) and
923 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
924 certmap[name] = cert[:-public_key_suffix_len]
925 else:
926 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
927
928 if not this_compressed_extension:
929 continue
930
931 # Only count the installed files.
932 filename = name + '.' + this_compressed_extension
933 if filename not in installed_files:
934 continue
935
936 # Make sure that all the values in the compression map have the same
937 # extension. We don't support multiple compression methods in the same
938 # system image.
939 if compressed_extension:
940 if this_compressed_extension != compressed_extension:
941 raise ValueError(
942 "Multiple compressed extensions: {} vs {}".format(
943 compressed_extension, this_compressed_extension))
944 else:
945 compressed_extension = this_compressed_extension
946
947 return (certmap,
948 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800949
950
Doug Zongkereef39442009-04-02 12:14:19 -0700951COMMON_DOCSTRING = """
952 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700953 Prepend <dir>/bin to the list of places to search for binaries
954 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700955
Doug Zongker05d3dea2009-06-22 11:32:31 -0700956 -s (--device_specific) <file>
957 Path to the python module containing device-specific
958 releasetools code.
959
Doug Zongker8bec09e2009-11-30 15:37:14 -0800960 -x (--extra) <key=value>
961 Add a key/value pair to the 'extras' dict, which device-specific
962 extension code may look at.
963
Doug Zongkereef39442009-04-02 12:14:19 -0700964 -v (--verbose)
965 Show command lines being executed.
966
967 -h (--help)
968 Display this usage message and exit.
969"""
970
971def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800972 print(docstring.rstrip("\n"))
973 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700974
975
976def ParseOptions(argv,
977 docstring,
978 extra_opts="", extra_long_opts=(),
979 extra_option_handler=None):
980 """Parse the options in argv and return any arguments that aren't
981 flags. docstring is the calling module's docstring, to be displayed
982 for errors and -h. extra_opts and extra_long_opts are for flags
983 defined by the caller, which are processed by passing them to
984 extra_option_handler."""
985
986 try:
987 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800988 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800989 ["help", "verbose", "path=", "signapk_path=",
990 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700991 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700992 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
993 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800994 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700995 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700996 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700997 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800998 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700999 sys.exit(2)
1000
Doug Zongkereef39442009-04-02 12:14:19 -07001001 for o, a in opts:
1002 if o in ("-h", "--help"):
1003 Usage(docstring)
1004 sys.exit()
1005 elif o in ("-v", "--verbose"):
1006 OPTIONS.verbose = True
1007 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001008 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001009 elif o in ("--signapk_path",):
1010 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001011 elif o in ("--signapk_shared_library_path",):
1012 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001013 elif o in ("--extra_signapk_args",):
1014 OPTIONS.extra_signapk_args = shlex.split(a)
1015 elif o in ("--java_path",):
1016 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001017 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001018 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001019 elif o in ("--public_key_suffix",):
1020 OPTIONS.public_key_suffix = a
1021 elif o in ("--private_key_suffix",):
1022 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001023 elif o in ("--boot_signer_path",):
1024 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001025 elif o in ("--boot_signer_args",):
1026 OPTIONS.boot_signer_args = shlex.split(a)
1027 elif o in ("--verity_signer_path",):
1028 OPTIONS.verity_signer_path = a
1029 elif o in ("--verity_signer_args",):
1030 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001031 elif o in ("-s", "--device_specific"):
1032 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001033 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001034 key, value = a.split("=", 1)
1035 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001036 else:
1037 if extra_option_handler is None or not extra_option_handler(o, a):
1038 assert False, "unknown option \"%s\"" % (o,)
1039
Doug Zongker85448772014-09-09 14:59:20 -07001040 if OPTIONS.search_path:
1041 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1042 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001043
1044 return args
1045
1046
Tao Bao4c851b12016-09-19 13:54:38 -07001047def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001048 """Make a temp file and add it to the list of things to be deleted
1049 when Cleanup() is called. Return the filename."""
1050 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1051 os.close(fd)
1052 OPTIONS.tempfiles.append(fn)
1053 return fn
1054
1055
Tao Bao1c830bf2017-12-25 10:43:47 -08001056def MakeTempDir(prefix='tmp', suffix=''):
1057 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1058
1059 Returns:
1060 The absolute pathname of the new directory.
1061 """
1062 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1063 OPTIONS.tempfiles.append(dir_name)
1064 return dir_name
1065
1066
Doug Zongkereef39442009-04-02 12:14:19 -07001067def Cleanup():
1068 for i in OPTIONS.tempfiles:
1069 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001070 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001071 else:
1072 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001073 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001074
1075
1076class PasswordManager(object):
1077 def __init__(self):
1078 self.editor = os.getenv("EDITOR", None)
1079 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
1080
1081 def GetPasswords(self, items):
1082 """Get passwords corresponding to each string in 'items',
1083 returning a dict. (The dict may have keys in addition to the
1084 values in 'items'.)
1085
1086 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1087 user edit that file to add more needed passwords. If no editor is
1088 available, or $ANDROID_PW_FILE isn't define, prompts the user
1089 interactively in the ordinary way.
1090 """
1091
1092 current = self.ReadFile()
1093
1094 first = True
1095 while True:
1096 missing = []
1097 for i in items:
1098 if i not in current or not current[i]:
1099 missing.append(i)
1100 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001101 if not missing:
1102 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001103
1104 for i in missing:
1105 current[i] = ""
1106
1107 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001108 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001109 answer = raw_input("try to edit again? [y]> ").strip()
1110 if answer and answer[0] not in 'yY':
1111 raise RuntimeError("key passwords unavailable")
1112 first = False
1113
1114 current = self.UpdateAndReadFile(current)
1115
Dan Albert8b72aef2015-03-23 19:13:21 -07001116 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001117 """Prompt the user to enter a value (password) for each key in
1118 'current' whose value is fales. Returns a new dict with all the
1119 values.
1120 """
1121 result = {}
1122 for k, v in sorted(current.iteritems()):
1123 if v:
1124 result[k] = v
1125 else:
1126 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001127 result[k] = getpass.getpass(
1128 "Enter password for %s key> " % k).strip()
1129 if result[k]:
1130 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001131 return result
1132
1133 def UpdateAndReadFile(self, current):
1134 if not self.editor or not self.pwfile:
1135 return self.PromptResult(current)
1136
1137 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001138 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001139 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1140 f.write("# (Additional spaces are harmless.)\n\n")
1141
1142 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001143 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1144 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001145 f.write("[[[ %s ]]] %s\n" % (v, k))
1146 if not v and first_line is None:
1147 # position cursor on first line with no password.
1148 first_line = i + 4
1149 f.close()
1150
1151 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1152 _, _ = p.communicate()
1153
1154 return self.ReadFile()
1155
1156 def ReadFile(self):
1157 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001158 if self.pwfile is None:
1159 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001160 try:
1161 f = open(self.pwfile, "r")
1162 for line in f:
1163 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001164 if not line or line[0] == '#':
1165 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001166 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1167 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001168 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001169 else:
1170 result[m.group(2)] = m.group(1)
1171 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001172 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001173 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001174 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001175 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001176
1177
Dan Albert8e0178d2015-01-27 15:53:15 -08001178def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1179 compress_type=None):
1180 import datetime
1181
1182 # http://b/18015246
1183 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1184 # for files larger than 2GiB. We can work around this by adjusting their
1185 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1186 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1187 # it isn't clear to me exactly what circumstances cause this).
1188 # `zipfile.write()` must be used directly to work around this.
1189 #
1190 # This mess can be avoided if we port to python3.
1191 saved_zip64_limit = zipfile.ZIP64_LIMIT
1192 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1193
1194 if compress_type is None:
1195 compress_type = zip_file.compression
1196 if arcname is None:
1197 arcname = filename
1198
1199 saved_stat = os.stat(filename)
1200
1201 try:
1202 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1203 # file to be zipped and reset it when we're done.
1204 os.chmod(filename, perms)
1205
1206 # Use a fixed timestamp so the output is repeatable.
1207 epoch = datetime.datetime.fromtimestamp(0)
1208 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1209 os.utime(filename, (timestamp, timestamp))
1210
1211 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1212 finally:
1213 os.chmod(filename, saved_stat.st_mode)
1214 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1215 zipfile.ZIP64_LIMIT = saved_zip64_limit
1216
1217
Tao Bao58c1b962015-05-20 09:32:18 -07001218def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001219 compress_type=None):
1220 """Wrap zipfile.writestr() function to work around the zip64 limit.
1221
1222 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1223 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1224 when calling crc32(bytes).
1225
1226 But it still works fine to write a shorter string into a large zip file.
1227 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1228 when we know the string won't be too long.
1229 """
1230
1231 saved_zip64_limit = zipfile.ZIP64_LIMIT
1232 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1233
1234 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1235 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001236 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001237 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001238 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001239 else:
Tao Baof3282b42015-04-01 11:21:55 -07001240 zinfo = zinfo_or_arcname
1241
1242 # If compress_type is given, it overrides the value in zinfo.
1243 if compress_type is not None:
1244 zinfo.compress_type = compress_type
1245
Tao Bao58c1b962015-05-20 09:32:18 -07001246 # If perms is given, it has a priority.
1247 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001248 # If perms doesn't set the file type, mark it as a regular file.
1249 if perms & 0o770000 == 0:
1250 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001251 zinfo.external_attr = perms << 16
1252
Tao Baof3282b42015-04-01 11:21:55 -07001253 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001254 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1255
Dan Albert8b72aef2015-03-23 19:13:21 -07001256 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001257 zipfile.ZIP64_LIMIT = saved_zip64_limit
1258
1259
Tao Bao89d7ab22017-12-14 17:05:33 -08001260def ZipDelete(zip_filename, entries):
1261 """Deletes entries from a ZIP file.
1262
1263 Since deleting entries from a ZIP file is not supported, it shells out to
1264 'zip -d'.
1265
1266 Args:
1267 zip_filename: The name of the ZIP file.
1268 entries: The name of the entry, or the list of names to be deleted.
1269
1270 Raises:
1271 AssertionError: In case of non-zero return from 'zip'.
1272 """
1273 if isinstance(entries, basestring):
1274 entries = [entries]
1275 cmd = ["zip", "-d", zip_filename] + entries
1276 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1277 stdoutdata, _ = proc.communicate()
1278 assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
1279 stdoutdata)
1280
1281
Tao Baof3282b42015-04-01 11:21:55 -07001282def ZipClose(zip_file):
1283 # http://b/18015246
1284 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1285 # central directory.
1286 saved_zip64_limit = zipfile.ZIP64_LIMIT
1287 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1288
1289 zip_file.close()
1290
1291 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001292
1293
1294class DeviceSpecificParams(object):
1295 module = None
1296 def __init__(self, **kwargs):
1297 """Keyword arguments to the constructor become attributes of this
1298 object, which is passed to all functions in the device-specific
1299 module."""
1300 for k, v in kwargs.iteritems():
1301 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001302 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001303
1304 if self.module is None:
1305 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001306 if not path:
1307 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001308 try:
1309 if os.path.isdir(path):
1310 info = imp.find_module("releasetools", [path])
1311 else:
1312 d, f = os.path.split(path)
1313 b, x = os.path.splitext(f)
1314 if x == ".py":
1315 f = b
1316 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001317 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001318 self.module = imp.load_module("device_specific", *info)
1319 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001320 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001321
1322 def _DoCall(self, function_name, *args, **kwargs):
1323 """Call the named function in the device-specific module, passing
1324 the given args and kwargs. The first argument to the call will be
1325 the DeviceSpecific object itself. If there is no module, or the
1326 module does not define the function, return the value of the
1327 'default' kwarg (which itself defaults to None)."""
1328 if self.module is None or not hasattr(self.module, function_name):
1329 return kwargs.get("default", None)
1330 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1331
1332 def FullOTA_Assertions(self):
1333 """Called after emitting the block of assertions at the top of a
1334 full OTA package. Implementations can add whatever additional
1335 assertions they like."""
1336 return self._DoCall("FullOTA_Assertions")
1337
Doug Zongkere5ff5902012-01-17 10:55:37 -08001338 def FullOTA_InstallBegin(self):
1339 """Called at the start of full OTA installation."""
1340 return self._DoCall("FullOTA_InstallBegin")
1341
Doug Zongker05d3dea2009-06-22 11:32:31 -07001342 def FullOTA_InstallEnd(self):
1343 """Called at the end of full OTA installation; typically this is
1344 used to install the image for the device's baseband processor."""
1345 return self._DoCall("FullOTA_InstallEnd")
1346
1347 def IncrementalOTA_Assertions(self):
1348 """Called after emitting the block of assertions at the top of an
1349 incremental OTA package. Implementations can add whatever
1350 additional assertions they like."""
1351 return self._DoCall("IncrementalOTA_Assertions")
1352
Doug Zongkere5ff5902012-01-17 10:55:37 -08001353 def IncrementalOTA_VerifyBegin(self):
1354 """Called at the start of the verification phase of incremental
1355 OTA installation; additional checks can be placed here to abort
1356 the script before any changes are made."""
1357 return self._DoCall("IncrementalOTA_VerifyBegin")
1358
Doug Zongker05d3dea2009-06-22 11:32:31 -07001359 def IncrementalOTA_VerifyEnd(self):
1360 """Called at the end of the verification phase of incremental OTA
1361 installation; additional checks can be placed here to abort the
1362 script before any changes are made."""
1363 return self._DoCall("IncrementalOTA_VerifyEnd")
1364
Doug Zongkere5ff5902012-01-17 10:55:37 -08001365 def IncrementalOTA_InstallBegin(self):
1366 """Called at the start of incremental OTA installation (after
1367 verification is complete)."""
1368 return self._DoCall("IncrementalOTA_InstallBegin")
1369
Doug Zongker05d3dea2009-06-22 11:32:31 -07001370 def IncrementalOTA_InstallEnd(self):
1371 """Called at the end of incremental OTA installation; typically
1372 this is used to install the image for the device's baseband
1373 processor."""
1374 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001375
Tao Bao9bc6bb22015-11-09 16:58:28 -08001376 def VerifyOTA_Assertions(self):
1377 return self._DoCall("VerifyOTA_Assertions")
1378
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001379class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001380 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001381 self.name = name
1382 self.data = data
1383 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001384 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001385 self.sha1 = sha1(data).hexdigest()
1386
1387 @classmethod
1388 def FromLocalFile(cls, name, diskname):
1389 f = open(diskname, "rb")
1390 data = f.read()
1391 f.close()
1392 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001393
1394 def WriteToTemp(self):
1395 t = tempfile.NamedTemporaryFile()
1396 t.write(self.data)
1397 t.flush()
1398 return t
1399
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001400 def WriteToDir(self, d):
1401 with open(os.path.join(d, self.name), "wb") as fp:
1402 fp.write(self.data)
1403
Geremy Condra36bd3652014-02-06 19:45:10 -08001404 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001405 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001406
1407DIFF_PROGRAM_BY_EXT = {
1408 ".gz" : "imgdiff",
1409 ".zip" : ["imgdiff", "-z"],
1410 ".jar" : ["imgdiff", "-z"],
1411 ".apk" : ["imgdiff", "-z"],
1412 ".img" : "imgdiff",
1413 }
1414
1415class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001416 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001417 self.tf = tf
1418 self.sf = sf
1419 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001420 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001421
1422 def ComputePatch(self):
1423 """Compute the patch (as a string of data) needed to turn sf into
1424 tf. Returns the same tuple as GetPatch()."""
1425
1426 tf = self.tf
1427 sf = self.sf
1428
Doug Zongker24cd2802012-08-14 16:36:15 -07001429 if self.diff_program:
1430 diff_program = self.diff_program
1431 else:
1432 ext = os.path.splitext(tf.name)[1]
1433 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001434
1435 ttemp = tf.WriteToTemp()
1436 stemp = sf.WriteToTemp()
1437
1438 ext = os.path.splitext(tf.name)[1]
1439
1440 try:
1441 ptemp = tempfile.NamedTemporaryFile()
1442 if isinstance(diff_program, list):
1443 cmd = copy.copy(diff_program)
1444 else:
1445 cmd = [diff_program]
1446 cmd.append(stemp.name)
1447 cmd.append(ttemp.name)
1448 cmd.append(ptemp.name)
1449 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001450 err = []
1451 def run():
1452 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001453 if e:
1454 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001455 th = threading.Thread(target=run)
1456 th.start()
1457 th.join(timeout=300) # 5 mins
1458 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001459 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001460 p.terminate()
1461 th.join(5)
1462 if th.is_alive():
1463 p.kill()
1464 th.join()
1465
Tianjie Xua2a9f992018-01-05 15:15:54 -08001466 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001467 print("WARNING: failure running %s:\n%s\n" % (
1468 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001469 self.patch = None
1470 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001471 diff = ptemp.read()
1472 finally:
1473 ptemp.close()
1474 stemp.close()
1475 ttemp.close()
1476
1477 self.patch = diff
1478 return self.tf, self.sf, self.patch
1479
1480
1481 def GetPatch(self):
1482 """Return a tuple (target_file, source_file, patch_data).
1483 patch_data may be None if ComputePatch hasn't been called, or if
1484 computing the patch failed."""
1485 return self.tf, self.sf, self.patch
1486
1487
1488def ComputeDifferences(diffs):
1489 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001490 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001491
1492 # Do the largest files first, to try and reduce the long-pole effect.
1493 by_size = [(i.tf.size, i) for i in diffs]
1494 by_size.sort(reverse=True)
1495 by_size = [i[1] for i in by_size]
1496
1497 lock = threading.Lock()
1498 diff_iter = iter(by_size) # accessed under lock
1499
1500 def worker():
1501 try:
1502 lock.acquire()
1503 for d in diff_iter:
1504 lock.release()
1505 start = time.time()
1506 d.ComputePatch()
1507 dur = time.time() - start
1508 lock.acquire()
1509
1510 tf, sf, patch = d.GetPatch()
1511 if sf.name == tf.name:
1512 name = tf.name
1513 else:
1514 name = "%s (%s)" % (tf.name, sf.name)
1515 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001516 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001517 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001518 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1519 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001520 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001521 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001522 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001523 raise
1524
1525 # start worker threads; wait for them all to finish.
1526 threads = [threading.Thread(target=worker)
1527 for i in range(OPTIONS.worker_threads)]
1528 for th in threads:
1529 th.start()
1530 while threads:
1531 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001532
1533
Dan Albert8b72aef2015-03-23 19:13:21 -07001534class BlockDifference(object):
1535 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001536 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001537 self.tgt = tgt
1538 self.src = src
1539 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001540 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001541 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001542
Tao Baodd2a5892015-03-12 12:32:37 -07001543 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001544 version = max(
1545 int(i) for i in
1546 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001547 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001548 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001549
1550 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001551 version=self.version,
1552 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001553 tmpdir = tempfile.mkdtemp()
1554 OPTIONS.tempfiles.append(tmpdir)
1555 self.path = os.path.join(tmpdir, partition)
1556 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001557 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001558 self.touched_src_ranges = b.touched_src_ranges
1559 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001560
Tao Baoaac4ad52015-10-16 15:26:34 -07001561 if src is None:
1562 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1563 else:
1564 _, self.device = GetTypeAndDevice("/" + partition,
1565 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001566
Tao Baod8d14be2016-02-04 14:26:02 -08001567 @property
1568 def required_cache(self):
1569 return self._required_cache
1570
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001571 def WriteScript(self, script, output_zip, progress=None):
1572 if not self.src:
1573 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001574 script.Print("Patching %s image unconditionally..." % (self.partition,))
1575 else:
1576 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001577
Dan Albert8b72aef2015-03-23 19:13:21 -07001578 if progress:
1579 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001580 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001581 if OPTIONS.verify:
1582 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001583
Tao Bao9bc6bb22015-11-09 16:58:28 -08001584 def WriteStrictVerifyScript(self, script):
1585 """Verify all the blocks in the care_map, including clobbered blocks.
1586
1587 This differs from the WriteVerifyScript() function: a) it prints different
1588 error messages; b) it doesn't allow half-way updated images to pass the
1589 verification."""
1590
1591 partition = self.partition
1592 script.Print("Verifying %s..." % (partition,))
1593 ranges = self.tgt.care_map
1594 ranges_str = ranges.to_string_raw()
1595 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1596 'ui_print(" Verified.") || '
1597 'ui_print("\\"%s\\" has unexpected contents.");' % (
1598 self.device, ranges_str,
1599 self.tgt.TotalSha1(include_clobbered_blocks=True),
1600 self.device))
1601 script.AppendExtra("")
1602
Tao Baod522bdc2016-04-12 15:53:16 -07001603 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001604 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001605
1606 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001607 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001608 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001609
1610 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001611 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001612 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001613 ranges = self.touched_src_ranges
1614 expected_sha1 = self.touched_src_sha1
1615 else:
1616 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1617 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001618
1619 # No blocks to be checked, skipping.
1620 if not ranges:
1621 return
1622
Tao Bao5ece99d2015-05-12 11:42:31 -07001623 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001624 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1625 'block_image_verify("%s", '
1626 'package_extract_file("%s.transfer.list"), '
1627 '"%s.new.dat", "%s.patch.dat")) then') % (
1628 self.device, ranges_str, expected_sha1,
1629 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001630 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001631 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001632
Tianjie Xufc3422a2015-12-15 11:53:59 -08001633 if self.version >= 4:
1634
1635 # Bug: 21124327
1636 # When generating incrementals for the system and vendor partitions in
1637 # version 4 or newer, explicitly check the first block (which contains
1638 # the superblock) of the partition to see if it's what we expect. If
1639 # this check fails, give an explicit log message about the partition
1640 # having been remounted R/W (the most likely explanation).
1641 if self.check_first_block:
1642 script.AppendExtra('check_first_block("%s");' % (self.device,))
1643
1644 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001645 if partition == "system":
1646 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1647 else:
1648 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001649 script.AppendExtra((
1650 'ifelse (block_image_recover("{device}", "{ranges}") && '
1651 'block_image_verify("{device}", '
1652 'package_extract_file("{partition}.transfer.list"), '
1653 '"{partition}.new.dat", "{partition}.patch.dat"), '
1654 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001655 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001656 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001657 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001658
Tao Baodd2a5892015-03-12 12:32:37 -07001659 # Abort the OTA update. Note that the incremental OTA cannot be applied
1660 # even if it may match the checksum of the target partition.
1661 # a) If version < 3, operations like move and erase will make changes
1662 # unconditionally and damage the partition.
1663 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001664 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001665 if partition == "system":
1666 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1667 else:
1668 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1669 script.AppendExtra((
1670 'abort("E%d: %s partition has unexpected contents");\n'
1671 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001672
Tao Bao5fcaaef2015-06-01 13:40:49 -07001673 def _WritePostInstallVerifyScript(self, script):
1674 partition = self.partition
1675 script.Print('Verifying the updated %s image...' % (partition,))
1676 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1677 ranges = self.tgt.care_map
1678 ranges_str = ranges.to_string_raw()
1679 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1680 self.device, ranges_str,
1681 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001682
1683 # Bug: 20881595
1684 # Verify that extended blocks are really zeroed out.
1685 if self.tgt.extended:
1686 ranges_str = self.tgt.extended.to_string_raw()
1687 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1688 self.device, ranges_str,
1689 self._HashZeroBlocks(self.tgt.extended.size())))
1690 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001691 if partition == "system":
1692 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1693 else:
1694 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001695 script.AppendExtra(
1696 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001697 ' abort("E%d: %s partition has unexpected non-zero contents after '
1698 'OTA update");\n'
1699 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001700 else:
1701 script.Print('Verified the updated %s image.' % (partition,))
1702
Tianjie Xu209db462016-05-24 17:34:52 -07001703 if partition == "system":
1704 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1705 else:
1706 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1707
Tao Bao5fcaaef2015-06-01 13:40:49 -07001708 script.AppendExtra(
1709 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001710 ' abort("E%d: %s partition has unexpected contents after OTA '
1711 'update");\n'
1712 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001713
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001714 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001715 ZipWrite(output_zip,
1716 '{}.transfer.list'.format(self.path),
1717 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001718
1719 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1720 # almost triples the compression time but doesn't further reduce the size too much.
1721 # For a typical 1.8G system.new.dat
1722 # zip | brotli(quality 6) | brotli(quality 9)
1723 # compressed_size: 942M | 869M (~8% reduced) | 854M
1724 # compression_time: 75s | 265s | 719s
1725 # decompression_time: 15s | 25s | 25s
1726
1727 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001728 brotli_cmd = ['brotli', '--quality=6',
1729 '--output={}.new.dat.br'.format(self.path),
1730 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001731 print("Compressing {}.new.dat with brotli".format(self.partition))
Alex Deymob10e07a2017-11-09 23:53:42 +01001732 p = Run(brotli_cmd, stdout=subprocess.PIPE)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001733 p.communicate()
1734 assert p.returncode == 0,\
1735 'compression of {}.new.dat failed'.format(self.partition)
1736
1737 new_data_name = '{}.new.dat.br'.format(self.partition)
1738 ZipWrite(output_zip,
1739 '{}.new.dat.br'.format(self.path),
1740 new_data_name,
1741 compress_type=zipfile.ZIP_STORED)
1742 else:
1743 new_data_name = '{}.new.dat'.format(self.partition)
1744 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1745
Dan Albert8e0178d2015-01-27 15:53:15 -08001746 ZipWrite(output_zip,
1747 '{}.patch.dat'.format(self.path),
1748 '{}.patch.dat'.format(self.partition),
1749 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001750
Tianjie Xu209db462016-05-24 17:34:52 -07001751 if self.partition == "system":
1752 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1753 else:
1754 code = ErrorCode.VENDOR_UPDATE_FAILURE
1755
Dan Albert8e0178d2015-01-27 15:53:15 -08001756 call = ('block_image_update("{device}", '
1757 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001758 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001759 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001760 device=self.device, partition=self.partition,
1761 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001762 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001763
Dan Albert8b72aef2015-03-23 19:13:21 -07001764 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001765 data = source.ReadRangeSet(ranges)
1766 ctx = sha1()
1767
1768 for p in data:
1769 ctx.update(p)
1770
1771 return ctx.hexdigest()
1772
Tao Baoe9b61912015-07-09 17:37:49 -07001773 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1774 """Return the hash value for all zero blocks."""
1775 zero_block = '\x00' * 4096
1776 ctx = sha1()
1777 for _ in range(num_blocks):
1778 ctx.update(zero_block)
1779
1780 return ctx.hexdigest()
1781
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001782
1783DataImage = blockimgdiff.DataImage
1784
Doug Zongker96a57e72010-09-26 14:57:41 -07001785# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001786PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001787 "ext4": "EMMC",
1788 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001789 "f2fs": "EMMC",
1790 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001791}
Doug Zongker96a57e72010-09-26 14:57:41 -07001792
1793def GetTypeAndDevice(mount_point, info):
1794 fstab = info["fstab"]
1795 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001796 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1797 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001798 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001799 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001800
1801
1802def ParseCertificate(data):
1803 """Parse a PEM-format certificate."""
1804 cert = []
1805 save = False
1806 for line in data.split("\n"):
1807 if "--END CERTIFICATE--" in line:
1808 break
1809 if save:
1810 cert.append(line)
1811 if "--BEGIN CERTIFICATE--" in line:
1812 save = True
1813 cert = "".join(cert).decode('base64')
1814 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001815
Tao Bao04e1f012018-02-04 12:13:35 -08001816
1817def ExtractPublicKey(cert):
1818 """Extracts the public key (PEM-encoded) from the given certificate file.
1819
1820 Args:
1821 cert: The certificate filename.
1822
1823 Returns:
1824 The public key string.
1825
1826 Raises:
1827 AssertionError: On non-zero return from 'openssl'.
1828 """
1829 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1830 # While openssl 1.1 writes the key into the given filename followed by '-out',
1831 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1832 # stdout instead.
1833 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1834 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1835 pubkey, stderrdata = proc.communicate()
1836 assert proc.returncode == 0, \
1837 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1838 return pubkey
1839
1840
Doug Zongker412c02f2014-02-13 10:58:24 -08001841def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1842 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001843 """Generate a binary patch that creates the recovery image starting
1844 with the boot image. (Most of the space in these images is just the
1845 kernel, which is identical for the two, so the resulting patch
1846 should be efficient.) Add it to the output zip, along with a shell
1847 script that is run from init.rc on first boot to actually do the
1848 patching and install the new recovery image.
1849
1850 recovery_img and boot_img should be File objects for the
1851 corresponding images. info should be the dictionary returned by
1852 common.LoadInfoDict() on the input target_files.
1853 """
1854
Doug Zongker412c02f2014-02-13 10:58:24 -08001855 if info_dict is None:
1856 info_dict = OPTIONS.info_dict
1857
Tao Baof2cffbd2015-07-22 12:33:18 -07001858 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001859
Tao Baof2cffbd2015-07-22 12:33:18 -07001860 if full_recovery_image:
1861 output_sink("etc/recovery.img", recovery_img.data)
1862
1863 else:
1864 diff_program = ["imgdiff"]
1865 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1866 if os.path.exists(path):
1867 diff_program.append("-b")
1868 diff_program.append(path)
1869 bonus_args = "-b /system/etc/recovery-resource.dat"
1870 else:
1871 bonus_args = ""
1872
1873 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1874 _, _, patch = d.ComputePatch()
1875 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001876
Dan Albertebb19aa2015-03-27 19:11:53 -07001877 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001878 # The following GetTypeAndDevice()s need to use the path in the target
1879 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001880 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1881 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1882 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001883 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001884
Tao Baof2cffbd2015-07-22 12:33:18 -07001885 if full_recovery_image:
1886 sh = """#!/system/bin/sh
1887if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1888 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"
1889else
1890 log -t recovery "Recovery image already installed"
1891fi
1892""" % {'type': recovery_type,
1893 'device': recovery_device,
1894 'sha1': recovery_img.sha1,
1895 'size': recovery_img.size}
1896 else:
1897 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001898if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1899 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"
1900else
1901 log -t recovery "Recovery image already installed"
1902fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001903""" % {'boot_size': boot_img.size,
1904 'boot_sha1': boot_img.sha1,
1905 'recovery_size': recovery_img.size,
1906 'recovery_sha1': recovery_img.sha1,
1907 'boot_type': boot_type,
1908 'boot_device': boot_device,
1909 'recovery_type': recovery_type,
1910 'recovery_device': recovery_device,
1911 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001912
1913 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001914 # in the L release.
1915 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001916
Tao Bao89fbb0f2017-01-10 10:47:58 -08001917 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001918
1919 output_sink(sh_location, sh)