blob: f80d0ec05cb1ec52290eb8c438226cf761ec944c [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
Tao Bao9dd909e2017-11-14 11:27:32 -080078
79# The partitions allowed to be signed by AVB (Android verified boot 2.0).
80AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'dtbo')
81
82
Tianjie Xu209db462016-05-24 17:34:52 -070083class ErrorCode(object):
84 """Define error_codes for failures that happen during the actual
85 update package installation.
86
87 Error codes 0-999 are reserved for failures before the package
88 installation (i.e. low battery, package verification failure).
89 Detailed code in 'bootable/recovery/error_code.h' """
90
91 SYSTEM_VERIFICATION_FAILURE = 1000
92 SYSTEM_UPDATE_FAILURE = 1001
93 SYSTEM_UNEXPECTED_CONTENTS = 1002
94 SYSTEM_NONZERO_CONTENTS = 1003
95 SYSTEM_RECOVER_FAILURE = 1004
96 VENDOR_VERIFICATION_FAILURE = 2000
97 VENDOR_UPDATE_FAILURE = 2001
98 VENDOR_UNEXPECTED_CONTENTS = 2002
99 VENDOR_NONZERO_CONTENTS = 2003
100 VENDOR_RECOVER_FAILURE = 2004
101 OEM_PROP_MISMATCH = 3000
102 FINGERPRINT_MISMATCH = 3001
103 THUMBPRINT_MISMATCH = 3002
104 OLDER_BUILD = 3003
105 DEVICE_MISMATCH = 3004
106 BAD_PATCH_FILE = 3005
107 INSUFFICIENT_CACHE_SPACE = 3006
108 TUNE_PARTITION_FAILURE = 3007
109 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800110
Dan Albert8b72aef2015-03-23 19:13:21 -0700111class ExternalError(RuntimeError):
112 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700113
114
Tao Bao39451582017-05-04 11:10:47 -0700115def Run(args, verbose=None, **kwargs):
116 """Create and return a subprocess.Popen object.
117
118 Caller can specify if the command line should be printed. The global
119 OPTIONS.verbose will be used if not specified.
120 """
121 if verbose is None:
122 verbose = OPTIONS.verbose
123 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800124 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700125 return subprocess.Popen(args, **kwargs)
126
127
Ying Wang7e6d4e42010-12-13 16:25:36 -0800128def CloseInheritedPipes():
129 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
130 before doing other work."""
131 if platform.system() != "Darwin":
132 return
133 for d in range(3, 1025):
134 try:
135 stat = os.fstat(d)
136 if stat is not None:
137 pipebit = stat[0] & 0x1000
138 if pipebit != 0:
139 os.close(d)
140 except OSError:
141 pass
142
143
Tao Bao2c15d9e2015-07-09 11:51:16 -0700144def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700145 """Read and parse the META/misc_info.txt key/value pairs from the
146 input target files and return a dict."""
147
Doug Zongkerc9253822014-02-04 12:17:58 -0800148 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700149 if isinstance(input_file, zipfile.ZipFile):
150 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800151 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700152 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800153 try:
154 with open(path) as f:
155 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700156 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800157 if e.errno == errno.ENOENT:
158 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800159
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700160 try:
Michael Runge6e836112014-04-15 17:40:21 -0700161 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700162 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800163 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700164
Tao Bao6cd54732017-02-27 15:12:05 -0800165 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800166 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800167
Tao Bao84e75682015-07-19 02:38:53 -0700168 # A few properties are stored as links to the files in the out/ directory.
169 # It works fine with the build system. However, they are no longer available
170 # when (re)generating from target_files zip. If input_dir is not None, we
171 # are doing repacking. Redirect those properties to the actual files in the
172 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700173 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400174 # We carry a copy of file_contexts.bin under META/. If not available,
175 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700176 # to build images than the one running on device, such as when enabling
177 # system_root_image. In that case, we must have the one for image
178 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700179 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
180 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700181 if d.get("system_root_image") == "true":
182 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700183 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700184 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700185 if not os.path.exists(fc_config):
186 fc_config = None
187
188 if fc_config:
189 d["selinux_fc"] = fc_config
190
Tao Bao84e75682015-07-19 02:38:53 -0700191 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
192 if d.get("system_root_image") == "true":
193 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
194 d["ramdisk_fs_config"] = os.path.join(
195 input_dir, "META", "root_filesystem_config.txt")
196
Tao Baof54216f2016-03-29 15:12:37 -0700197 # Redirect {system,vendor}_base_fs_file.
198 if "system_base_fs_file" in d:
199 basename = os.path.basename(d["system_base_fs_file"])
200 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700201 if os.path.exists(system_base_fs_file):
202 d["system_base_fs_file"] = system_base_fs_file
203 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800204 print("Warning: failed to find system base fs file: %s" % (
205 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700206 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700207
208 if "vendor_base_fs_file" in d:
209 basename = os.path.basename(d["vendor_base_fs_file"])
210 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700211 if os.path.exists(vendor_base_fs_file):
212 d["vendor_base_fs_file"] = vendor_base_fs_file
213 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800214 print("Warning: failed to find vendor base fs file: %s" % (
215 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700216 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700217
Doug Zongker37974732010-09-16 17:44:38 -0700218 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800219 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700220 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700221 if not line:
222 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700223 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700224 if not value:
225 continue
Doug Zongker37974732010-09-16 17:44:38 -0700226 if name == "blocksize":
227 d[name] = value
228 else:
229 d[name + "_size"] = value
230 except KeyError:
231 pass
232
233 def makeint(key):
234 if key in d:
235 d[key] = int(d[key], 0)
236
237 makeint("recovery_api_version")
238 makeint("blocksize")
239 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700240 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700241 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700242 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700243 makeint("recovery_size")
244 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800245 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700246
Tianjie Xucfa86222016-03-07 16:31:19 -0800247 system_root_image = d.get("system_root_image", None) == "true"
248 if d.get("no_recovery", None) != "true":
249 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800250 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800251 recovery_fstab_path, system_root_image)
252 elif d.get("recovery_as_boot", None) == "true":
253 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
254 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
255 recovery_fstab_path, system_root_image)
256 else:
257 d["fstab"] = None
258
Tao Baobcd1d162017-08-26 13:10:26 -0700259 d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
260 d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700261 return d
262
Tao Baod1de6f32017-03-01 16:38:48 -0800263
Tao Baobcd1d162017-08-26 13:10:26 -0700264def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700265 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700266 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700267 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700268 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700269 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700270 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700271
Tao Baod1de6f32017-03-01 16:38:48 -0800272
Michael Runge6e836112014-04-15 17:40:21 -0700273def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700274 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700275 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700276 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700277 if not line or line.startswith("#"):
278 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700279 if "=" in line:
280 name, value = line.split("=", 1)
281 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700282 return d
283
Tao Baod1de6f32017-03-01 16:38:48 -0800284
Tianjie Xucfa86222016-03-07 16:31:19 -0800285def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
286 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700287 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800288 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700289 self.mount_point = mount_point
290 self.fs_type = fs_type
291 self.device = device
292 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700293 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700294
295 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800296 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700297 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800298 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700299 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700300
Tao Baod1de6f32017-03-01 16:38:48 -0800301 assert fstab_version == 2
302
303 d = {}
304 for line in data.split("\n"):
305 line = line.strip()
306 if not line or line.startswith("#"):
307 continue
308
309 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
310 pieces = line.split()
311 if len(pieces) != 5:
312 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
313
314 # Ignore entries that are managed by vold.
315 options = pieces[4]
316 if "voldmanaged=" in options:
317 continue
318
319 # It's a good line, parse it.
320 length = 0
321 options = options.split(",")
322 for i in options:
323 if i.startswith("length="):
324 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800325 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800326 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700327 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800328
Tao Baod1de6f32017-03-01 16:38:48 -0800329 mount_flags = pieces[3]
330 # Honor the SELinux context if present.
331 context = None
332 for i in mount_flags.split(","):
333 if i.startswith("context="):
334 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800335
Tao Baod1de6f32017-03-01 16:38:48 -0800336 mount_point = pieces[1]
337 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
338 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800339
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700340 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700341 # system. Other areas assume system is always at "/system" so point /system
342 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700343 if system_root_image:
344 assert not d.has_key("/system") and d.has_key("/")
345 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700346 return d
347
348
Doug Zongker37974732010-09-16 17:44:38 -0700349def DumpInfoDict(d):
350 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800351 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700352
Dan Albert8b72aef2015-03-23 19:13:21 -0700353
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800354def AppendAVBSigningArgs(cmd, partition):
355 """Append signing arguments for avbtool."""
356 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
357 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
358 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
359 if key_path and algorithm:
360 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700361 avb_salt = OPTIONS.info_dict.get("avb_salt")
362 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
363 if avb_salt and partition != "vbmeta":
364 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800365
366
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700367def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800368 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700369 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700370
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700371 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800372 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
373 we are building a two-step special image (i.e. building a recovery image to
374 be loaded into /boot in two-step OTAs).
375
376 Return the image data, or None if sourcedir does not appear to contains files
377 for building the requested image.
378 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700379
380 def make_ramdisk():
381 ramdisk_img = tempfile.NamedTemporaryFile()
382
383 if os.access(fs_config_file, os.F_OK):
384 cmd = ["mkbootfs", "-f", fs_config_file,
385 os.path.join(sourcedir, "RAMDISK")]
386 else:
387 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
388 p1 = Run(cmd, stdout=subprocess.PIPE)
389 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
390
391 p2.wait()
392 p1.wait()
393 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
394 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
395
396 return ramdisk_img
397
398 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
399 return None
400
401 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700402 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700403
Doug Zongkerd5131602012-08-02 14:46:42 -0700404 if info_dict is None:
405 info_dict = OPTIONS.info_dict
406
Doug Zongkereef39442009-04-02 12:14:19 -0700407 img = tempfile.NamedTemporaryFile()
408
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700409 if has_ramdisk:
410 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700411
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800412 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
413 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
414
415 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700416
Benoit Fradina45a8682014-07-14 21:00:43 +0200417 fn = os.path.join(sourcedir, "second")
418 if os.access(fn, os.F_OK):
419 cmd.append("--second")
420 cmd.append(fn)
421
Doug Zongker171f1cd2009-06-15 22:36:37 -0700422 fn = os.path.join(sourcedir, "cmdline")
423 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700424 cmd.append("--cmdline")
425 cmd.append(open(fn).read().rstrip("\n"))
426
427 fn = os.path.join(sourcedir, "base")
428 if os.access(fn, os.F_OK):
429 cmd.append("--base")
430 cmd.append(open(fn).read().rstrip("\n"))
431
Ying Wang4de6b5b2010-08-25 14:29:34 -0700432 fn = os.path.join(sourcedir, "pagesize")
433 if os.access(fn, os.F_OK):
434 cmd.append("--pagesize")
435 cmd.append(open(fn).read().rstrip("\n"))
436
Doug Zongkerd5131602012-08-02 14:46:42 -0700437 args = info_dict.get("mkbootimg_args", None)
438 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700439 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700440
Sami Tolvanen3303d902016-03-15 16:49:30 +0000441 args = info_dict.get("mkbootimg_version_args", None)
442 if args and args.strip():
443 cmd.extend(shlex.split(args))
444
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700445 if has_ramdisk:
446 cmd.extend(["--ramdisk", ramdisk_img.name])
447
Tao Baod95e9fd2015-03-29 23:07:41 -0700448 img_unsigned = None
449 if info_dict.get("vboot", None):
450 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700451 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700452 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700453 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700454
455 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700456 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700457 assert p.returncode == 0, "mkbootimg of %s image failed" % (
458 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700459
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100460 if (info_dict.get("boot_signer", None) == "true" and
461 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800462 # Hard-code the path as "/boot" for two-step special recovery image (which
463 # will be loaded into /boot during the two-step OTA).
464 if two_step_image:
465 path = "/boot"
466 else:
467 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700468 cmd = [OPTIONS.boot_signer_path]
469 cmd.extend(OPTIONS.boot_signer_args)
470 cmd.extend([path, img.name,
471 info_dict["verity_key"] + ".pk8",
472 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700473 p = Run(cmd, stdout=subprocess.PIPE)
474 p.communicate()
475 assert p.returncode == 0, "boot_signer of %s image failed" % path
476
Tao Baod95e9fd2015-03-29 23:07:41 -0700477 # Sign the image if vboot is non-empty.
478 elif info_dict.get("vboot", None):
479 path = "/" + os.path.basename(sourcedir).lower()
480 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800481 # We have switched from the prebuilt futility binary to using the tool
482 # (futility-host) built from the source. Override the setting in the old
483 # TF.zip.
484 futility = info_dict["futility"]
485 if futility.startswith("prebuilts/"):
486 futility = "futility-host"
487 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700488 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700489 info_dict["vboot_key"] + ".vbprivk",
490 info_dict["vboot_subkey"] + ".vbprivk",
491 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700492 img.name]
493 p = Run(cmd, stdout=subprocess.PIPE)
494 p.communicate()
495 assert p.returncode == 0, "vboot_signer of %s image failed" % path
496
Tao Baof3282b42015-04-01 11:21:55 -0700497 # Clean up the temp files.
498 img_unsigned.close()
499 img_keyblock.close()
500
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400501 # AVB: if enabled, calculate and add hash to boot.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800502 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700503 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
504 part_size = info_dict["boot_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400505 cmd = [avbtool, "add_hash_footer", "--image", img.name,
506 "--partition_size", str(part_size), "--partition_name", "boot"]
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800507 AppendAVBSigningArgs(cmd, "boot")
508 args = info_dict.get("avb_boot_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400509 if args and args.strip():
510 cmd.extend(shlex.split(args))
511 p = Run(cmd, stdout=subprocess.PIPE)
512 p.communicate()
513 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
514 os.path.basename(OPTIONS.input_tmp))
David Zeuthend995f4b2016-01-29 16:59:17 -0500515
516 img.seek(os.SEEK_SET, 0)
517 data = img.read()
518
519 if has_ramdisk:
520 ramdisk_img.close()
521 img.close()
522
523 return data
524
525
Doug Zongkerd5131602012-08-02 14:46:42 -0700526def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800527 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700528 """Return a File object with the desired bootable image.
529
530 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
531 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
532 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700533
Doug Zongker55d93282011-01-25 17:03:34 -0800534 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
535 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800536 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800537 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700538
539 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
540 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800541 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700542 return File.FromLocalFile(name, prebuilt_path)
543
Tao Bao89fbb0f2017-01-10 10:47:58 -0800544 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700545
546 if info_dict is None:
547 info_dict = OPTIONS.info_dict
548
549 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800550 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
551 # for recovery.
552 has_ramdisk = (info_dict.get("system_root_image") != "true" or
553 prebuilt_name != "boot.img" or
554 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700555
Doug Zongker6f1d0312014-08-22 08:07:12 -0700556 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400557 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
558 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800559 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700560 if data:
561 return File(name, data)
562 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800563
Doug Zongkereef39442009-04-02 12:14:19 -0700564
Doug Zongker75f17362009-12-08 13:46:44 -0800565def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800566 """Unzip the given archive into a temporary directory and return the name.
567
568 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
569 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
570
571 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
572 main file), open for reading.
573 """
Doug Zongkereef39442009-04-02 12:14:19 -0700574
575 tmp = tempfile.mkdtemp(prefix="targetfiles-")
576 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800577
578 def unzip_to_dir(filename, dirname):
579 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
580 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800581 cmd.extend(pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800582 p = Run(cmd, stdout=subprocess.PIPE)
583 p.communicate()
584 if p.returncode != 0:
585 raise ExternalError("failed to unzip input target-files \"%s\"" %
586 (filename,))
587
588 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
589 if m:
590 unzip_to_dir(m.group(1), tmp)
591 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
592 filename = m.group(1)
593 else:
594 unzip_to_dir(filename, tmp)
595
596 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700597
598
599def GetKeyPasswords(keylist):
600 """Given a list of keys, prompt the user to enter passwords for
601 those which require them. Return a {key: password} dict. password
602 will be None if the key has no password."""
603
Doug Zongker8ce7c252009-05-22 13:34:54 -0700604 no_passwords = []
605 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700606 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700607 devnull = open("/dev/null", "w+b")
608 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800609 # We don't need a password for things that aren't really keys.
610 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700611 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700612 continue
613
T.R. Fullhart37e10522013-03-18 10:31:26 -0700614 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700615 "-inform", "DER", "-nocrypt"],
616 stdin=devnull.fileno(),
617 stdout=devnull.fileno(),
618 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700619 p.communicate()
620 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700621 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700622 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700623 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700624 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
625 "-inform", "DER", "-passin", "pass:"],
626 stdin=devnull.fileno(),
627 stdout=devnull.fileno(),
628 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700629 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700630 if p.returncode == 0:
631 # Encrypted key with empty string as password.
632 key_passwords[k] = ''
633 elif stderr.startswith('Error decrypting key'):
634 # Definitely encrypted key.
635 # It would have said "Error reading key" if it didn't parse correctly.
636 need_passwords.append(k)
637 else:
638 # Potentially, a type of key that openssl doesn't understand.
639 # We'll let the routines in signapk.jar handle it.
640 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700641 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700642
T.R. Fullhart37e10522013-03-18 10:31:26 -0700643 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700644 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700645 return key_passwords
646
647
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800648def GetMinSdkVersion(apk_name):
649 """Get the minSdkVersion delared in the APK. This can be both a decimal number
650 (API Level) or a codename.
651 """
652
653 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
654 output, err = p.communicate()
655 if err:
656 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
657 % (p.returncode,))
658
659 for line in output.split("\n"):
660 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
661 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
662 if m:
663 return m.group(1)
664 raise ExternalError("No minSdkVersion returned by aapt")
665
666
667def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
668 """Get the minSdkVersion declared in the APK as a number (API Level). If
669 minSdkVersion is set to a codename, it is translated to a number using the
670 provided map.
671 """
672
673 version = GetMinSdkVersion(apk_name)
674 try:
675 return int(version)
676 except ValueError:
677 # Not a decimal number. Codename?
678 if version in codename_to_api_level_map:
679 return codename_to_api_level_map[version]
680 else:
681 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
682 % (version, codename_to_api_level_map))
683
684
685def SignFile(input_name, output_name, key, password, min_api_level=None,
686 codename_to_api_level_map=dict(),
687 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700688 """Sign the input_name zip/jar/apk, producing output_name. Use the
689 given key and password (the latter may be None if the key does not
690 have a password.
691
Doug Zongker951495f2009-08-14 12:44:19 -0700692 If whole_file is true, use the "-w" option to SignApk to embed a
693 signature that covers the whole file in the archive comment of the
694 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800695
696 min_api_level is the API Level (int) of the oldest platform this file may end
697 up on. If not specified for an APK, the API Level is obtained by interpreting
698 the minSdkVersion attribute of the APK's AndroidManifest.xml.
699
700 codename_to_api_level_map is needed to translate the codename which may be
701 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700702 """
Doug Zongker951495f2009-08-14 12:44:19 -0700703
Alex Klyubin9667b182015-12-10 13:38:50 -0800704 java_library_path = os.path.join(
705 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
706
Tao Baoe95540e2016-11-08 12:08:53 -0800707 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
708 ["-Djava.library.path=" + java_library_path,
709 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
710 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700711 if whole_file:
712 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800713
714 min_sdk_version = min_api_level
715 if min_sdk_version is None:
716 if not whole_file:
717 min_sdk_version = GetMinSdkVersionInt(
718 input_name, codename_to_api_level_map)
719 if min_sdk_version is not None:
720 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
721
T.R. Fullhart37e10522013-03-18 10:31:26 -0700722 cmd.extend([key + OPTIONS.public_key_suffix,
723 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800724 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700725
726 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700727 if password is not None:
728 password += "\n"
729 p.communicate(password)
730 if p.returncode != 0:
731 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
732
Doug Zongkereef39442009-04-02 12:14:19 -0700733
Doug Zongker37974732010-09-16 17:44:38 -0700734def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800735 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700736
Tao Bao9dd909e2017-11-14 11:27:32 -0800737 For non-AVB images, raise exception if the data is too big. Print a warning
738 if the data is nearing the maximum size.
739
740 For AVB images, the actual image size should be identical to the limit.
741
742 Args:
743 data: A string that contains all the data for the partition.
744 target: The partition name. The ".img" suffix is optional.
745 info_dict: The dict to be looked up for relevant info.
746 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700747 if target.endswith(".img"):
748 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700749 mount_point = "/" + target
750
Ying Wangf8824af2014-06-03 14:07:27 -0700751 fs_type = None
752 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700753 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700754 if mount_point == "/userdata":
755 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700756 p = info_dict["fstab"][mount_point]
757 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800758 device = p.device
759 if "/" in device:
760 device = device[device.rfind("/")+1:]
761 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700762 if not fs_type or not limit:
763 return
Doug Zongkereef39442009-04-02 12:14:19 -0700764
Andrew Boie0f9aec82012-02-14 09:32:52 -0800765 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800766 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
767 # path.
768 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
769 if size != limit:
770 raise ExternalError(
771 "Mismatching image size for %s: expected %d actual %d" % (
772 target, limit, size))
773 else:
774 pct = float(size) * 100.0 / limit
775 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
776 if pct >= 99.0:
777 raise ExternalError(msg)
778 elif pct >= 95.0:
779 print("\n WARNING: %s\n" % (msg,))
780 elif OPTIONS.verbose:
781 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700782
783
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800784def ReadApkCerts(tf_zip):
785 """Given a target_files ZipFile, parse the META/apkcerts.txt file
786 and return a {package: cert} dict."""
787 certmap = {}
788 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
789 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700790 if not line:
791 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800792 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
793 r'private_key="(.*)"$', line)
794 if m:
795 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700796 public_key_suffix_len = len(OPTIONS.public_key_suffix)
797 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800798 if cert in SPECIAL_CERT_STRINGS and not privkey:
799 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700800 elif (cert.endswith(OPTIONS.public_key_suffix) and
801 privkey.endswith(OPTIONS.private_key_suffix) and
802 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
803 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800804 else:
805 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
806 return certmap
807
808
Doug Zongkereef39442009-04-02 12:14:19 -0700809COMMON_DOCSTRING = """
810 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700811 Prepend <dir>/bin to the list of places to search for binaries
812 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700813
Doug Zongker05d3dea2009-06-22 11:32:31 -0700814 -s (--device_specific) <file>
815 Path to the python module containing device-specific
816 releasetools code.
817
Doug Zongker8bec09e2009-11-30 15:37:14 -0800818 -x (--extra) <key=value>
819 Add a key/value pair to the 'extras' dict, which device-specific
820 extension code may look at.
821
Doug Zongkereef39442009-04-02 12:14:19 -0700822 -v (--verbose)
823 Show command lines being executed.
824
825 -h (--help)
826 Display this usage message and exit.
827"""
828
829def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800830 print(docstring.rstrip("\n"))
831 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700832
833
834def ParseOptions(argv,
835 docstring,
836 extra_opts="", extra_long_opts=(),
837 extra_option_handler=None):
838 """Parse the options in argv and return any arguments that aren't
839 flags. docstring is the calling module's docstring, to be displayed
840 for errors and -h. extra_opts and extra_long_opts are for flags
841 defined by the caller, which are processed by passing them to
842 extra_option_handler."""
843
844 try:
845 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800846 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800847 ["help", "verbose", "path=", "signapk_path=",
848 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700849 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700850 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
851 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800852 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700853 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700854 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700855 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800856 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700857 sys.exit(2)
858
Doug Zongkereef39442009-04-02 12:14:19 -0700859 for o, a in opts:
860 if o in ("-h", "--help"):
861 Usage(docstring)
862 sys.exit()
863 elif o in ("-v", "--verbose"):
864 OPTIONS.verbose = True
865 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700866 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700867 elif o in ("--signapk_path",):
868 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800869 elif o in ("--signapk_shared_library_path",):
870 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700871 elif o in ("--extra_signapk_args",):
872 OPTIONS.extra_signapk_args = shlex.split(a)
873 elif o in ("--java_path",):
874 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700875 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800876 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700877 elif o in ("--public_key_suffix",):
878 OPTIONS.public_key_suffix = a
879 elif o in ("--private_key_suffix",):
880 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800881 elif o in ("--boot_signer_path",):
882 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700883 elif o in ("--boot_signer_args",):
884 OPTIONS.boot_signer_args = shlex.split(a)
885 elif o in ("--verity_signer_path",):
886 OPTIONS.verity_signer_path = a
887 elif o in ("--verity_signer_args",):
888 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700889 elif o in ("-s", "--device_specific"):
890 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800891 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800892 key, value = a.split("=", 1)
893 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700894 else:
895 if extra_option_handler is None or not extra_option_handler(o, a):
896 assert False, "unknown option \"%s\"" % (o,)
897
Doug Zongker85448772014-09-09 14:59:20 -0700898 if OPTIONS.search_path:
899 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
900 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700901
902 return args
903
904
Tao Bao4c851b12016-09-19 13:54:38 -0700905def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700906 """Make a temp file and add it to the list of things to be deleted
907 when Cleanup() is called. Return the filename."""
908 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
909 os.close(fd)
910 OPTIONS.tempfiles.append(fn)
911 return fn
912
913
Doug Zongkereef39442009-04-02 12:14:19 -0700914def Cleanup():
915 for i in OPTIONS.tempfiles:
916 if os.path.isdir(i):
917 shutil.rmtree(i)
918 else:
919 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700920
921
922class PasswordManager(object):
923 def __init__(self):
924 self.editor = os.getenv("EDITOR", None)
925 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
926
927 def GetPasswords(self, items):
928 """Get passwords corresponding to each string in 'items',
929 returning a dict. (The dict may have keys in addition to the
930 values in 'items'.)
931
932 Uses the passwords in $ANDROID_PW_FILE if available, letting the
933 user edit that file to add more needed passwords. If no editor is
934 available, or $ANDROID_PW_FILE isn't define, prompts the user
935 interactively in the ordinary way.
936 """
937
938 current = self.ReadFile()
939
940 first = True
941 while True:
942 missing = []
943 for i in items:
944 if i not in current or not current[i]:
945 missing.append(i)
946 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700947 if not missing:
948 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700949
950 for i in missing:
951 current[i] = ""
952
953 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800954 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700955 answer = raw_input("try to edit again? [y]> ").strip()
956 if answer and answer[0] not in 'yY':
957 raise RuntimeError("key passwords unavailable")
958 first = False
959
960 current = self.UpdateAndReadFile(current)
961
Dan Albert8b72aef2015-03-23 19:13:21 -0700962 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700963 """Prompt the user to enter a value (password) for each key in
964 'current' whose value is fales. Returns a new dict with all the
965 values.
966 """
967 result = {}
968 for k, v in sorted(current.iteritems()):
969 if v:
970 result[k] = v
971 else:
972 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700973 result[k] = getpass.getpass(
974 "Enter password for %s key> " % k).strip()
975 if result[k]:
976 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700977 return result
978
979 def UpdateAndReadFile(self, current):
980 if not self.editor or not self.pwfile:
981 return self.PromptResult(current)
982
983 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700984 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700985 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
986 f.write("# (Additional spaces are harmless.)\n\n")
987
988 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700989 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
990 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700991 f.write("[[[ %s ]]] %s\n" % (v, k))
992 if not v and first_line is None:
993 # position cursor on first line with no password.
994 first_line = i + 4
995 f.close()
996
997 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
998 _, _ = p.communicate()
999
1000 return self.ReadFile()
1001
1002 def ReadFile(self):
1003 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001004 if self.pwfile is None:
1005 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001006 try:
1007 f = open(self.pwfile, "r")
1008 for line in f:
1009 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001010 if not line or line[0] == '#':
1011 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001012 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1013 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001014 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001015 else:
1016 result[m.group(2)] = m.group(1)
1017 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001018 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001019 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001020 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001021 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001022
1023
Dan Albert8e0178d2015-01-27 15:53:15 -08001024def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1025 compress_type=None):
1026 import datetime
1027
1028 # http://b/18015246
1029 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1030 # for files larger than 2GiB. We can work around this by adjusting their
1031 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1032 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1033 # it isn't clear to me exactly what circumstances cause this).
1034 # `zipfile.write()` must be used directly to work around this.
1035 #
1036 # This mess can be avoided if we port to python3.
1037 saved_zip64_limit = zipfile.ZIP64_LIMIT
1038 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1039
1040 if compress_type is None:
1041 compress_type = zip_file.compression
1042 if arcname is None:
1043 arcname = filename
1044
1045 saved_stat = os.stat(filename)
1046
1047 try:
1048 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1049 # file to be zipped and reset it when we're done.
1050 os.chmod(filename, perms)
1051
1052 # Use a fixed timestamp so the output is repeatable.
1053 epoch = datetime.datetime.fromtimestamp(0)
1054 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1055 os.utime(filename, (timestamp, timestamp))
1056
1057 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1058 finally:
1059 os.chmod(filename, saved_stat.st_mode)
1060 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1061 zipfile.ZIP64_LIMIT = saved_zip64_limit
1062
1063
Tao Bao58c1b962015-05-20 09:32:18 -07001064def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001065 compress_type=None):
1066 """Wrap zipfile.writestr() function to work around the zip64 limit.
1067
1068 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1069 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1070 when calling crc32(bytes).
1071
1072 But it still works fine to write a shorter string into a large zip file.
1073 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1074 when we know the string won't be too long.
1075 """
1076
1077 saved_zip64_limit = zipfile.ZIP64_LIMIT
1078 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1079
1080 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1081 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001082 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001083 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001084 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001085 else:
Tao Baof3282b42015-04-01 11:21:55 -07001086 zinfo = zinfo_or_arcname
1087
1088 # If compress_type is given, it overrides the value in zinfo.
1089 if compress_type is not None:
1090 zinfo.compress_type = compress_type
1091
Tao Bao58c1b962015-05-20 09:32:18 -07001092 # If perms is given, it has a priority.
1093 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001094 # If perms doesn't set the file type, mark it as a regular file.
1095 if perms & 0o770000 == 0:
1096 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001097 zinfo.external_attr = perms << 16
1098
Tao Baof3282b42015-04-01 11:21:55 -07001099 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001100 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1101
Dan Albert8b72aef2015-03-23 19:13:21 -07001102 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001103 zipfile.ZIP64_LIMIT = saved_zip64_limit
1104
1105
1106def ZipClose(zip_file):
1107 # http://b/18015246
1108 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1109 # central directory.
1110 saved_zip64_limit = zipfile.ZIP64_LIMIT
1111 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1112
1113 zip_file.close()
1114
1115 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001116
1117
1118class DeviceSpecificParams(object):
1119 module = None
1120 def __init__(self, **kwargs):
1121 """Keyword arguments to the constructor become attributes of this
1122 object, which is passed to all functions in the device-specific
1123 module."""
1124 for k, v in kwargs.iteritems():
1125 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001126 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001127
1128 if self.module is None:
1129 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001130 if not path:
1131 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001132 try:
1133 if os.path.isdir(path):
1134 info = imp.find_module("releasetools", [path])
1135 else:
1136 d, f = os.path.split(path)
1137 b, x = os.path.splitext(f)
1138 if x == ".py":
1139 f = b
1140 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001141 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001142 self.module = imp.load_module("device_specific", *info)
1143 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001144 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001145
1146 def _DoCall(self, function_name, *args, **kwargs):
1147 """Call the named function in the device-specific module, passing
1148 the given args and kwargs. The first argument to the call will be
1149 the DeviceSpecific object itself. If there is no module, or the
1150 module does not define the function, return the value of the
1151 'default' kwarg (which itself defaults to None)."""
1152 if self.module is None or not hasattr(self.module, function_name):
1153 return kwargs.get("default", None)
1154 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1155
1156 def FullOTA_Assertions(self):
1157 """Called after emitting the block of assertions at the top of a
1158 full OTA package. Implementations can add whatever additional
1159 assertions they like."""
1160 return self._DoCall("FullOTA_Assertions")
1161
Doug Zongkere5ff5902012-01-17 10:55:37 -08001162 def FullOTA_InstallBegin(self):
1163 """Called at the start of full OTA installation."""
1164 return self._DoCall("FullOTA_InstallBegin")
1165
Doug Zongker05d3dea2009-06-22 11:32:31 -07001166 def FullOTA_InstallEnd(self):
1167 """Called at the end of full OTA installation; typically this is
1168 used to install the image for the device's baseband processor."""
1169 return self._DoCall("FullOTA_InstallEnd")
1170
1171 def IncrementalOTA_Assertions(self):
1172 """Called after emitting the block of assertions at the top of an
1173 incremental OTA package. Implementations can add whatever
1174 additional assertions they like."""
1175 return self._DoCall("IncrementalOTA_Assertions")
1176
Doug Zongkere5ff5902012-01-17 10:55:37 -08001177 def IncrementalOTA_VerifyBegin(self):
1178 """Called at the start of the verification phase of incremental
1179 OTA installation; additional checks can be placed here to abort
1180 the script before any changes are made."""
1181 return self._DoCall("IncrementalOTA_VerifyBegin")
1182
Doug Zongker05d3dea2009-06-22 11:32:31 -07001183 def IncrementalOTA_VerifyEnd(self):
1184 """Called at the end of the verification phase of incremental OTA
1185 installation; additional checks can be placed here to abort the
1186 script before any changes are made."""
1187 return self._DoCall("IncrementalOTA_VerifyEnd")
1188
Doug Zongkere5ff5902012-01-17 10:55:37 -08001189 def IncrementalOTA_InstallBegin(self):
1190 """Called at the start of incremental OTA installation (after
1191 verification is complete)."""
1192 return self._DoCall("IncrementalOTA_InstallBegin")
1193
Doug Zongker05d3dea2009-06-22 11:32:31 -07001194 def IncrementalOTA_InstallEnd(self):
1195 """Called at the end of incremental OTA installation; typically
1196 this is used to install the image for the device's baseband
1197 processor."""
1198 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001199
Tao Bao9bc6bb22015-11-09 16:58:28 -08001200 def VerifyOTA_Assertions(self):
1201 return self._DoCall("VerifyOTA_Assertions")
1202
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001203class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001204 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001205 self.name = name
1206 self.data = data
1207 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001208 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001209 self.sha1 = sha1(data).hexdigest()
1210
1211 @classmethod
1212 def FromLocalFile(cls, name, diskname):
1213 f = open(diskname, "rb")
1214 data = f.read()
1215 f.close()
1216 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001217
1218 def WriteToTemp(self):
1219 t = tempfile.NamedTemporaryFile()
1220 t.write(self.data)
1221 t.flush()
1222 return t
1223
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001224 def WriteToDir(self, d):
1225 with open(os.path.join(d, self.name), "wb") as fp:
1226 fp.write(self.data)
1227
Geremy Condra36bd3652014-02-06 19:45:10 -08001228 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001229 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001230
1231DIFF_PROGRAM_BY_EXT = {
1232 ".gz" : "imgdiff",
1233 ".zip" : ["imgdiff", "-z"],
1234 ".jar" : ["imgdiff", "-z"],
1235 ".apk" : ["imgdiff", "-z"],
1236 ".img" : "imgdiff",
1237 }
1238
1239class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001240 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001241 self.tf = tf
1242 self.sf = sf
1243 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001244 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001245
1246 def ComputePatch(self):
1247 """Compute the patch (as a string of data) needed to turn sf into
1248 tf. Returns the same tuple as GetPatch()."""
1249
1250 tf = self.tf
1251 sf = self.sf
1252
Doug Zongker24cd2802012-08-14 16:36:15 -07001253 if self.diff_program:
1254 diff_program = self.diff_program
1255 else:
1256 ext = os.path.splitext(tf.name)[1]
1257 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001258
1259 ttemp = tf.WriteToTemp()
1260 stemp = sf.WriteToTemp()
1261
1262 ext = os.path.splitext(tf.name)[1]
1263
1264 try:
1265 ptemp = tempfile.NamedTemporaryFile()
1266 if isinstance(diff_program, list):
1267 cmd = copy.copy(diff_program)
1268 else:
1269 cmd = [diff_program]
1270 cmd.append(stemp.name)
1271 cmd.append(ttemp.name)
1272 cmd.append(ptemp.name)
1273 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001274 err = []
1275 def run():
1276 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001277 if e:
1278 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001279 th = threading.Thread(target=run)
1280 th.start()
1281 th.join(timeout=300) # 5 mins
1282 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001283 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001284 p.terminate()
1285 th.join(5)
1286 if th.is_alive():
1287 p.kill()
1288 th.join()
1289
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001290 if err or p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001291 print("WARNING: failure running %s:\n%s\n" % (
1292 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001293 self.patch = None
1294 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001295 diff = ptemp.read()
1296 finally:
1297 ptemp.close()
1298 stemp.close()
1299 ttemp.close()
1300
1301 self.patch = diff
1302 return self.tf, self.sf, self.patch
1303
1304
1305 def GetPatch(self):
1306 """Return a tuple (target_file, source_file, patch_data).
1307 patch_data may be None if ComputePatch hasn't been called, or if
1308 computing the patch failed."""
1309 return self.tf, self.sf, self.patch
1310
1311
1312def ComputeDifferences(diffs):
1313 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001314 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001315
1316 # Do the largest files first, to try and reduce the long-pole effect.
1317 by_size = [(i.tf.size, i) for i in diffs]
1318 by_size.sort(reverse=True)
1319 by_size = [i[1] for i in by_size]
1320
1321 lock = threading.Lock()
1322 diff_iter = iter(by_size) # accessed under lock
1323
1324 def worker():
1325 try:
1326 lock.acquire()
1327 for d in diff_iter:
1328 lock.release()
1329 start = time.time()
1330 d.ComputePatch()
1331 dur = time.time() - start
1332 lock.acquire()
1333
1334 tf, sf, patch = d.GetPatch()
1335 if sf.name == tf.name:
1336 name = tf.name
1337 else:
1338 name = "%s (%s)" % (tf.name, sf.name)
1339 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001340 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001341 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001342 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1343 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001344 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001345 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001346 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001347 raise
1348
1349 # start worker threads; wait for them all to finish.
1350 threads = [threading.Thread(target=worker)
1351 for i in range(OPTIONS.worker_threads)]
1352 for th in threads:
1353 th.start()
1354 while threads:
1355 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001356
1357
Dan Albert8b72aef2015-03-23 19:13:21 -07001358class BlockDifference(object):
1359 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001360 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001361 self.tgt = tgt
1362 self.src = src
1363 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001364 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001365 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001366
Tao Baodd2a5892015-03-12 12:32:37 -07001367 if version is None:
1368 version = 1
1369 if OPTIONS.info_dict:
1370 version = max(
1371 int(i) for i in
1372 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001373 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001374 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001375
1376 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001377 version=self.version,
1378 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001379 tmpdir = tempfile.mkdtemp()
1380 OPTIONS.tempfiles.append(tmpdir)
1381 self.path = os.path.join(tmpdir, partition)
1382 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001383 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001384 self.touched_src_ranges = b.touched_src_ranges
1385 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001386
Tao Baoaac4ad52015-10-16 15:26:34 -07001387 if src is None:
1388 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1389 else:
1390 _, self.device = GetTypeAndDevice("/" + partition,
1391 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001392
Tao Baod8d14be2016-02-04 14:26:02 -08001393 @property
1394 def required_cache(self):
1395 return self._required_cache
1396
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001397 def WriteScript(self, script, output_zip, progress=None):
1398 if not self.src:
1399 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001400 script.Print("Patching %s image unconditionally..." % (self.partition,))
1401 else:
1402 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001403
Dan Albert8b72aef2015-03-23 19:13:21 -07001404 if progress:
1405 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001406 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001407 if OPTIONS.verify:
1408 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001409
Tao Bao9bc6bb22015-11-09 16:58:28 -08001410 def WriteStrictVerifyScript(self, script):
1411 """Verify all the blocks in the care_map, including clobbered blocks.
1412
1413 This differs from the WriteVerifyScript() function: a) it prints different
1414 error messages; b) it doesn't allow half-way updated images to pass the
1415 verification."""
1416
1417 partition = self.partition
1418 script.Print("Verifying %s..." % (partition,))
1419 ranges = self.tgt.care_map
1420 ranges_str = ranges.to_string_raw()
1421 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1422 'ui_print(" Verified.") || '
1423 'ui_print("\\"%s\\" has unexpected contents.");' % (
1424 self.device, ranges_str,
1425 self.tgt.TotalSha1(include_clobbered_blocks=True),
1426 self.device))
1427 script.AppendExtra("")
1428
Tao Baod522bdc2016-04-12 15:53:16 -07001429 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001430 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001431
1432 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001433 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001434 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001435
1436 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001437 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001438 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001439 ranges = self.touched_src_ranges
1440 expected_sha1 = self.touched_src_sha1
1441 else:
1442 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1443 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001444
1445 # No blocks to be checked, skipping.
1446 if not ranges:
1447 return
1448
Tao Bao5ece99d2015-05-12 11:42:31 -07001449 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001450 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1451 'block_image_verify("%s", '
1452 'package_extract_file("%s.transfer.list"), '
1453 '"%s.new.dat", "%s.patch.dat")) then') % (
1454 self.device, ranges_str, expected_sha1,
1455 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001456 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001457 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001458
Tianjie Xufc3422a2015-12-15 11:53:59 -08001459 if self.version >= 4:
1460
1461 # Bug: 21124327
1462 # When generating incrementals for the system and vendor partitions in
1463 # version 4 or newer, explicitly check the first block (which contains
1464 # the superblock) of the partition to see if it's what we expect. If
1465 # this check fails, give an explicit log message about the partition
1466 # having been remounted R/W (the most likely explanation).
1467 if self.check_first_block:
1468 script.AppendExtra('check_first_block("%s");' % (self.device,))
1469
1470 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001471 if partition == "system":
1472 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1473 else:
1474 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001475 script.AppendExtra((
1476 'ifelse (block_image_recover("{device}", "{ranges}") && '
1477 'block_image_verify("{device}", '
1478 'package_extract_file("{partition}.transfer.list"), '
1479 '"{partition}.new.dat", "{partition}.patch.dat"), '
1480 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001481 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001482 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001483 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001484
Tao Baodd2a5892015-03-12 12:32:37 -07001485 # Abort the OTA update. Note that the incremental OTA cannot be applied
1486 # even if it may match the checksum of the target partition.
1487 # a) If version < 3, operations like move and erase will make changes
1488 # unconditionally and damage the partition.
1489 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001490 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001491 if partition == "system":
1492 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1493 else:
1494 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1495 script.AppendExtra((
1496 'abort("E%d: %s partition has unexpected contents");\n'
1497 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001498
Tao Bao5fcaaef2015-06-01 13:40:49 -07001499 def _WritePostInstallVerifyScript(self, script):
1500 partition = self.partition
1501 script.Print('Verifying the updated %s image...' % (partition,))
1502 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1503 ranges = self.tgt.care_map
1504 ranges_str = ranges.to_string_raw()
1505 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1506 self.device, ranges_str,
1507 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001508
1509 # Bug: 20881595
1510 # Verify that extended blocks are really zeroed out.
1511 if self.tgt.extended:
1512 ranges_str = self.tgt.extended.to_string_raw()
1513 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1514 self.device, ranges_str,
1515 self._HashZeroBlocks(self.tgt.extended.size())))
1516 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001517 if partition == "system":
1518 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1519 else:
1520 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001521 script.AppendExtra(
1522 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001523 ' abort("E%d: %s partition has unexpected non-zero contents after '
1524 'OTA update");\n'
1525 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001526 else:
1527 script.Print('Verified the updated %s image.' % (partition,))
1528
Tianjie Xu209db462016-05-24 17:34:52 -07001529 if partition == "system":
1530 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1531 else:
1532 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1533
Tao Bao5fcaaef2015-06-01 13:40:49 -07001534 script.AppendExtra(
1535 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001536 ' abort("E%d: %s partition has unexpected contents after OTA '
1537 'update");\n'
1538 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001539
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001540 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001541 ZipWrite(output_zip,
1542 '{}.transfer.list'.format(self.path),
1543 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001544
1545 # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
1546 # almost triples the compression time but doesn't further reduce the size too much.
1547 # For a typical 1.8G system.new.dat
1548 # zip | brotli(quality 6) | brotli(quality 9)
1549 # compressed_size: 942M | 869M (~8% reduced) | 854M
1550 # compression_time: 75s | 265s | 719s
1551 # decompression_time: 15s | 25s | 25s
1552
1553 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001554 brotli_cmd = ['brotli', '--quality=6',
1555 '--output={}.new.dat.br'.format(self.path),
1556 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001557 print("Compressing {}.new.dat with brotli".format(self.partition))
Alex Deymob10e07a2017-11-09 23:53:42 +01001558 p = Run(brotli_cmd, stdout=subprocess.PIPE)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001559 p.communicate()
1560 assert p.returncode == 0,\
1561 'compression of {}.new.dat failed'.format(self.partition)
1562
1563 new_data_name = '{}.new.dat.br'.format(self.partition)
1564 ZipWrite(output_zip,
1565 '{}.new.dat.br'.format(self.path),
1566 new_data_name,
1567 compress_type=zipfile.ZIP_STORED)
1568 else:
1569 new_data_name = '{}.new.dat'.format(self.partition)
1570 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1571
Dan Albert8e0178d2015-01-27 15:53:15 -08001572 ZipWrite(output_zip,
1573 '{}.patch.dat'.format(self.path),
1574 '{}.patch.dat'.format(self.partition),
1575 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001576
Tianjie Xu209db462016-05-24 17:34:52 -07001577 if self.partition == "system":
1578 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1579 else:
1580 code = ErrorCode.VENDOR_UPDATE_FAILURE
1581
Dan Albert8e0178d2015-01-27 15:53:15 -08001582 call = ('block_image_update("{device}", '
1583 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001584 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001585 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001586 device=self.device, partition=self.partition,
1587 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001588 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001589
Dan Albert8b72aef2015-03-23 19:13:21 -07001590 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001591 data = source.ReadRangeSet(ranges)
1592 ctx = sha1()
1593
1594 for p in data:
1595 ctx.update(p)
1596
1597 return ctx.hexdigest()
1598
Tao Baoe9b61912015-07-09 17:37:49 -07001599 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1600 """Return the hash value for all zero blocks."""
1601 zero_block = '\x00' * 4096
1602 ctx = sha1()
1603 for _ in range(num_blocks):
1604 ctx.update(zero_block)
1605
1606 return ctx.hexdigest()
1607
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001608
1609DataImage = blockimgdiff.DataImage
1610
Doug Zongker96a57e72010-09-26 14:57:41 -07001611# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001612PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001613 "ext4": "EMMC",
1614 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001615 "f2fs": "EMMC",
1616 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001617}
Doug Zongker96a57e72010-09-26 14:57:41 -07001618
1619def GetTypeAndDevice(mount_point, info):
1620 fstab = info["fstab"]
1621 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001622 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1623 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001624 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001625 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001626
1627
1628def ParseCertificate(data):
1629 """Parse a PEM-format certificate."""
1630 cert = []
1631 save = False
1632 for line in data.split("\n"):
1633 if "--END CERTIFICATE--" in line:
1634 break
1635 if save:
1636 cert.append(line)
1637 if "--BEGIN CERTIFICATE--" in line:
1638 save = True
1639 cert = "".join(cert).decode('base64')
1640 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001641
Doug Zongker412c02f2014-02-13 10:58:24 -08001642def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1643 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001644 """Generate a binary patch that creates the recovery image starting
1645 with the boot image. (Most of the space in these images is just the
1646 kernel, which is identical for the two, so the resulting patch
1647 should be efficient.) Add it to the output zip, along with a shell
1648 script that is run from init.rc on first boot to actually do the
1649 patching and install the new recovery image.
1650
1651 recovery_img and boot_img should be File objects for the
1652 corresponding images. info should be the dictionary returned by
1653 common.LoadInfoDict() on the input target_files.
1654 """
1655
Doug Zongker412c02f2014-02-13 10:58:24 -08001656 if info_dict is None:
1657 info_dict = OPTIONS.info_dict
1658
Tao Baof2cffbd2015-07-22 12:33:18 -07001659 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001660
Tao Baof2cffbd2015-07-22 12:33:18 -07001661 if full_recovery_image:
1662 output_sink("etc/recovery.img", recovery_img.data)
1663
1664 else:
1665 diff_program = ["imgdiff"]
1666 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1667 if os.path.exists(path):
1668 diff_program.append("-b")
1669 diff_program.append(path)
1670 bonus_args = "-b /system/etc/recovery-resource.dat"
1671 else:
1672 bonus_args = ""
1673
1674 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1675 _, _, patch = d.ComputePatch()
1676 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001677
Dan Albertebb19aa2015-03-27 19:11:53 -07001678 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001679 # The following GetTypeAndDevice()s need to use the path in the target
1680 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001681 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1682 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1683 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001684 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001685
Tao Baof2cffbd2015-07-22 12:33:18 -07001686 if full_recovery_image:
1687 sh = """#!/system/bin/sh
1688if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1689 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"
1690else
1691 log -t recovery "Recovery image already installed"
1692fi
1693""" % {'type': recovery_type,
1694 'device': recovery_device,
1695 'sha1': recovery_img.sha1,
1696 'size': recovery_img.size}
1697 else:
1698 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001699if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1700 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"
1701else
1702 log -t recovery "Recovery image already installed"
1703fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001704""" % {'boot_size': boot_img.size,
1705 'boot_sha1': boot_img.sha1,
1706 'recovery_size': recovery_img.size,
1707 'recovery_sha1': recovery_img.sha1,
1708 'boot_type': boot_type,
1709 'boot_device': boot_device,
1710 'recovery_type': recovery_type,
1711 'recovery_device': recovery_device,
1712 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001713
1714 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07001715 # in the L release.
1716 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001717
Tao Bao89fbb0f2017-01-10 10:47:58 -08001718 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001719
1720 output_sink(sh_location, sh)