blob: d1bfc8f2f3aa7804a6f7707a658fc6cdd0217271 [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
Tao Bao986ee862018-10-04 15:46:16 -070040
Dan Albert8b72aef2015-03-23 19:13:21 -070041class Options(object):
42 def __init__(self):
43 platform_search_path = {
44 "linux2": "out/host/linux-x86",
45 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070046 }
Doug Zongker85448772014-09-09 14:59:20 -070047
Tao Bao76def242017-11-21 09:25:31 -080048 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070049 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080050 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070051 self.extra_signapk_args = []
52 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080053 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070054 self.public_key_suffix = ".x509.pem"
55 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070056 # use otatools built boot_signer by default
57 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070058 self.boot_signer_args = []
59 self.verity_signer_path = None
60 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070061 self.verbose = False
62 self.tempfiles = []
63 self.device_specific = None
64 self.extras = {}
65 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070066 self.source_info_dict = None
67 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070068 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070069 # Stash size cannot exceed cache_size * threshold.
70 self.cache_size = None
71 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070072
73
74OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070075
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080076# Values for "certificate" in apkcerts that mean special things.
77SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
78
Tao Bao9dd909e2017-11-14 11:27:32 -080079# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010080AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010081 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080082
Tianjie Xu861f4132018-09-12 11:49:33 -070083# Partitions that should have their care_map added to META/care_map.pb
84PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
85 'odm')
86
87
Tianjie Xu209db462016-05-24 17:34:52 -070088class ErrorCode(object):
89 """Define error_codes for failures that happen during the actual
90 update package installation.
91
92 Error codes 0-999 are reserved for failures before the package
93 installation (i.e. low battery, package verification failure).
94 Detailed code in 'bootable/recovery/error_code.h' """
95
96 SYSTEM_VERIFICATION_FAILURE = 1000
97 SYSTEM_UPDATE_FAILURE = 1001
98 SYSTEM_UNEXPECTED_CONTENTS = 1002
99 SYSTEM_NONZERO_CONTENTS = 1003
100 SYSTEM_RECOVER_FAILURE = 1004
101 VENDOR_VERIFICATION_FAILURE = 2000
102 VENDOR_UPDATE_FAILURE = 2001
103 VENDOR_UNEXPECTED_CONTENTS = 2002
104 VENDOR_NONZERO_CONTENTS = 2003
105 VENDOR_RECOVER_FAILURE = 2004
106 OEM_PROP_MISMATCH = 3000
107 FINGERPRINT_MISMATCH = 3001
108 THUMBPRINT_MISMATCH = 3002
109 OLDER_BUILD = 3003
110 DEVICE_MISMATCH = 3004
111 BAD_PATCH_FILE = 3005
112 INSUFFICIENT_CACHE_SPACE = 3006
113 TUNE_PARTITION_FAILURE = 3007
114 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800115
Tao Bao80921982018-03-21 21:02:19 -0700116
Dan Albert8b72aef2015-03-23 19:13:21 -0700117class ExternalError(RuntimeError):
118 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700119
120
Tao Bao39451582017-05-04 11:10:47 -0700121def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700122 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700123
Tao Bao73dd4f42018-10-04 16:25:33 -0700124 Args:
125 args: The command represented as a list of strings.
126 verbose: Whether the commands should be shown (default to OPTIONS.verbose
127 if unspecified).
128 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
129 stdin, etc. stdout and stderr will default to subprocess.PIPE and
130 subprocess.STDOUT respectively unless caller specifies any of them.
131
132 Returns:
133 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700134 """
135 if verbose is None:
136 verbose = OPTIONS.verbose
Tao Bao73dd4f42018-10-04 16:25:33 -0700137 if 'stdout' not in kwargs and 'stderr' not in kwargs:
138 kwargs['stdout'] = subprocess.PIPE
139 kwargs['stderr'] = subprocess.STDOUT
Tao Bao39451582017-05-04 11:10:47 -0700140 if verbose:
Tao Bao73dd4f42018-10-04 16:25:33 -0700141 print(" Running: \"{}\"".format(" ".join(args)))
Doug Zongkereef39442009-04-02 12:14:19 -0700142 return subprocess.Popen(args, **kwargs)
143
144
Tao Bao986ee862018-10-04 15:46:16 -0700145def RunAndCheckOutput(args, verbose=None, **kwargs):
146 """Runs the given command and returns the output.
147
148 Args:
149 args: The command represented as a list of strings.
150 verbose: Whether the commands should be shown (default to OPTIONS.verbose
151 if unspecified).
152 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
153 stdin, etc. stdout and stderr will default to subprocess.PIPE and
154 subprocess.STDOUT respectively unless caller specifies any of them.
155
156 Returns:
157 The output string.
158
159 Raises:
160 ExternalError: On non-zero exit from the command.
161 """
162 if verbose is None:
163 verbose = OPTIONS.verbose
164 proc = Run(args, verbose=verbose, **kwargs)
165 output, _ = proc.communicate()
166 if verbose:
167 print("{}".format(output.rstrip()))
168 if proc.returncode != 0:
169 raise ExternalError(
170 "Failed to run command '{}' (exit code {}):\n{}".format(
171 args, proc.returncode, output))
172 return output
173
174
Tao Baoc765cca2018-01-31 17:32:40 -0800175def RoundUpTo4K(value):
176 rounded_up = value + 4095
177 return rounded_up - (rounded_up % 4096)
178
179
Ying Wang7e6d4e42010-12-13 16:25:36 -0800180def CloseInheritedPipes():
181 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
182 before doing other work."""
183 if platform.system() != "Darwin":
184 return
185 for d in range(3, 1025):
186 try:
187 stat = os.fstat(d)
188 if stat is not None:
189 pipebit = stat[0] & 0x1000
190 if pipebit != 0:
191 os.close(d)
192 except OSError:
193 pass
194
195
Tao Bao410ad8b2018-08-24 12:08:38 -0700196def LoadInfoDict(input_file, repacking=False):
197 """Loads the key/value pairs from the given input target_files.
198
199 It reads `META/misc_info.txt` file in the target_files input, does sanity
200 checks and returns the parsed key/value pairs for to the given build. It's
201 usually called early when working on input target_files files, e.g. when
202 generating OTAs, or signing builds. Note that the function may be called
203 against an old target_files file (i.e. from past dessert releases). So the
204 property parsing needs to be backward compatible.
205
206 In a `META/misc_info.txt`, a few properties are stored as links to the files
207 in the PRODUCT_OUT directory. It works fine with the build system. However,
208 they are no longer available when (re)generating images from target_files zip.
209 When `repacking` is True, redirect these properties to the actual files in the
210 unzipped directory.
211
212 Args:
213 input_file: The input target_files file, which could be an open
214 zipfile.ZipFile instance, or a str for the dir that contains the files
215 unzipped from a target_files file.
216 repacking: Whether it's trying repack an target_files file after loading the
217 info dict (default: False). If so, it will rewrite a few loaded
218 properties (e.g. selinux_fc, root_dir) to point to the actual files in
219 target_files file. When doing repacking, `input_file` must be a dir.
220
221 Returns:
222 A dict that contains the parsed key/value pairs.
223
224 Raises:
225 AssertionError: On invalid input arguments.
226 ValueError: On malformed input values.
227 """
228 if repacking:
229 assert isinstance(input_file, str), \
230 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700231
Doug Zongkerc9253822014-02-04 12:17:58 -0800232 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700233 if isinstance(input_file, zipfile.ZipFile):
234 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800235 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700236 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800237 try:
238 with open(path) as f:
239 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700240 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800241 if e.errno == errno.ENOENT:
242 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800243
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700244 try:
Michael Runge6e836112014-04-15 17:40:21 -0700245 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700246 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700247 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700248
Tao Bao410ad8b2018-08-24 12:08:38 -0700249 if "recovery_api_version" not in d:
250 raise ValueError("Failed to find 'recovery_api_version'")
251 if "fstab_version" not in d:
252 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800253
Tao Bao410ad8b2018-08-24 12:08:38 -0700254 if repacking:
255 # We carry a copy of file_contexts.bin under META/. If not available, search
256 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
257 # images than the one running on device, in that case, we must have the one
258 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700259 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700260 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700261 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700262
Tom Cherryd14b8952018-08-09 14:26:00 -0700263 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700264
Tom Cherryd14b8952018-08-09 14:26:00 -0700265 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700266 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700267 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700268 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700269
Tao Baof54216f2016-03-29 15:12:37 -0700270 # Redirect {system,vendor}_base_fs_file.
271 if "system_base_fs_file" in d:
272 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700273 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700274 if os.path.exists(system_base_fs_file):
275 d["system_base_fs_file"] = system_base_fs_file
276 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800277 print("Warning: failed to find system base fs file: %s" % (
278 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700279 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700280
281 if "vendor_base_fs_file" in d:
282 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700283 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700284 if os.path.exists(vendor_base_fs_file):
285 d["vendor_base_fs_file"] = vendor_base_fs_file
286 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800287 print("Warning: failed to find vendor base fs file: %s" % (
288 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700289 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700290
Doug Zongker37974732010-09-16 17:44:38 -0700291 def makeint(key):
292 if key in d:
293 d[key] = int(d[key], 0)
294
295 makeint("recovery_api_version")
296 makeint("blocksize")
297 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700298 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700299 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700300 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700301 makeint("recovery_size")
302 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800303 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700304
Tao Baoa57ab9f2018-08-24 12:08:38 -0700305 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
306 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
307 # cases, since it may load the info_dict from an old build (e.g. when
308 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800309 system_root_image = d.get("system_root_image") == "true"
310 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700311 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700312 if isinstance(input_file, zipfile.ZipFile):
313 if recovery_fstab_path not in input_file.namelist():
314 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
315 else:
316 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
317 if not os.path.exists(path):
318 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800319 d["fstab"] = LoadRecoveryFSTab(
320 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700321
Tao Bao76def242017-11-21 09:25:31 -0800322 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700323 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700324 if isinstance(input_file, zipfile.ZipFile):
325 if recovery_fstab_path not in input_file.namelist():
326 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
327 else:
328 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
329 if not os.path.exists(path):
330 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800331 d["fstab"] = LoadRecoveryFSTab(
332 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700333
Tianjie Xucfa86222016-03-07 16:31:19 -0800334 else:
335 d["fstab"] = None
336
Tianjie Xu861f4132018-09-12 11:49:33 -0700337 # Tries to load the build props for all partitions with care_map, including
338 # system and vendor.
339 for partition in PARTITIONS_WITH_CARE_MAP:
340 d["{}.build.prop".format(partition)] = LoadBuildProp(
341 read_helper, "{}/build.prop".format(partition.upper()))
342 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800343
344 # Set up the salt (based on fingerprint or thumbprint) that will be used when
345 # adding AVB footer.
346 if d.get("avb_enable") == "true":
347 fp = None
348 if "build.prop" in d:
349 build_prop = d["build.prop"]
350 if "ro.build.fingerprint" in build_prop:
351 fp = build_prop["ro.build.fingerprint"]
352 elif "ro.build.thumbprint" in build_prop:
353 fp = build_prop["ro.build.thumbprint"]
354 if fp:
355 d["avb_salt"] = sha256(fp).hexdigest()
356
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700357 return d
358
Tao Baod1de6f32017-03-01 16:38:48 -0800359
Tao Baobcd1d162017-08-26 13:10:26 -0700360def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700361 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700362 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700363 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700364 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700365 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700366 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700367
Tao Baod1de6f32017-03-01 16:38:48 -0800368
Michael Runge6e836112014-04-15 17:40:21 -0700369def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700370 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700371 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700372 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700373 if not line or line.startswith("#"):
374 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700375 if "=" in line:
376 name, value = line.split("=", 1)
377 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700378 return d
379
Tao Baod1de6f32017-03-01 16:38:48 -0800380
Tianjie Xucfa86222016-03-07 16:31:19 -0800381def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
382 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700383 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800384 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700385 self.mount_point = mount_point
386 self.fs_type = fs_type
387 self.device = device
388 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700389 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700390
391 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800392 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700393 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800394 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700395 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700396
Tao Baod1de6f32017-03-01 16:38:48 -0800397 assert fstab_version == 2
398
399 d = {}
400 for line in data.split("\n"):
401 line = line.strip()
402 if not line or line.startswith("#"):
403 continue
404
405 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
406 pieces = line.split()
407 if len(pieces) != 5:
408 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
409
410 # Ignore entries that are managed by vold.
411 options = pieces[4]
412 if "voldmanaged=" in options:
413 continue
414
415 # It's a good line, parse it.
416 length = 0
417 options = options.split(",")
418 for i in options:
419 if i.startswith("length="):
420 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800421 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800422 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700423 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800424
Tao Baod1de6f32017-03-01 16:38:48 -0800425 mount_flags = pieces[3]
426 # Honor the SELinux context if present.
427 context = None
428 for i in mount_flags.split(","):
429 if i.startswith("context="):
430 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800431
Tao Baod1de6f32017-03-01 16:38:48 -0800432 mount_point = pieces[1]
433 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
434 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800435
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700436 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700437 # system. Other areas assume system is always at "/system" so point /system
438 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700439 if system_root_image:
440 assert not d.has_key("/system") and d.has_key("/")
441 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700442 return d
443
444
Doug Zongker37974732010-09-16 17:44:38 -0700445def DumpInfoDict(d):
446 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800447 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700448
Dan Albert8b72aef2015-03-23 19:13:21 -0700449
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800450def AppendAVBSigningArgs(cmd, partition):
451 """Append signing arguments for avbtool."""
452 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
453 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
454 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
455 if key_path and algorithm:
456 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700457 avb_salt = OPTIONS.info_dict.get("avb_salt")
458 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700459 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700460 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800461
462
Tao Bao02a08592018-07-22 12:40:45 -0700463def GetAvbChainedPartitionArg(partition, info_dict, key=None):
464 """Constructs and returns the arg to build or verify a chained partition.
465
466 Args:
467 partition: The partition name.
468 info_dict: The info dict to look up the key info and rollback index
469 location.
470 key: The key to be used for building or verifying the partition. Defaults to
471 the key listed in info_dict.
472
473 Returns:
474 A string of form "partition:rollback_index_location:key" that can be used to
475 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700476 """
477 if key is None:
478 key = info_dict["avb_" + partition + "_key_path"]
479 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
480 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
Tao Bao986ee862018-10-04 15:46:16 -0700481 RunAndCheckOutput(
Tao Bao73dd4f42018-10-04 16:25:33 -0700482 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path])
Tao Bao02a08592018-07-22 12:40:45 -0700483
484 rollback_index_location = info_dict[
485 "avb_" + partition + "_rollback_index_location"]
486 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
487
488
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700489def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800490 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700491 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700492
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700493 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800494 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
495 we are building a two-step special image (i.e. building a recovery image to
496 be loaded into /boot in two-step OTAs).
497
498 Return the image data, or None if sourcedir does not appear to contains files
499 for building the requested image.
500 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700501
502 def make_ramdisk():
503 ramdisk_img = tempfile.NamedTemporaryFile()
504
505 if os.access(fs_config_file, os.F_OK):
506 cmd = ["mkbootfs", "-f", fs_config_file,
507 os.path.join(sourcedir, "RAMDISK")]
508 else:
509 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
510 p1 = Run(cmd, stdout=subprocess.PIPE)
511 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
512
513 p2.wait()
514 p1.wait()
515 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
516 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
517
518 return ramdisk_img
519
520 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
521 return None
522
523 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700524 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700525
Doug Zongkerd5131602012-08-02 14:46:42 -0700526 if info_dict is None:
527 info_dict = OPTIONS.info_dict
528
Doug Zongkereef39442009-04-02 12:14:19 -0700529 img = tempfile.NamedTemporaryFile()
530
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700531 if has_ramdisk:
532 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700533
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800534 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
535 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
536
537 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700538
Benoit Fradina45a8682014-07-14 21:00:43 +0200539 fn = os.path.join(sourcedir, "second")
540 if os.access(fn, os.F_OK):
541 cmd.append("--second")
542 cmd.append(fn)
543
Doug Zongker171f1cd2009-06-15 22:36:37 -0700544 fn = os.path.join(sourcedir, "cmdline")
545 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700546 cmd.append("--cmdline")
547 cmd.append(open(fn).read().rstrip("\n"))
548
549 fn = os.path.join(sourcedir, "base")
550 if os.access(fn, os.F_OK):
551 cmd.append("--base")
552 cmd.append(open(fn).read().rstrip("\n"))
553
Ying Wang4de6b5b2010-08-25 14:29:34 -0700554 fn = os.path.join(sourcedir, "pagesize")
555 if os.access(fn, os.F_OK):
556 cmd.append("--pagesize")
557 cmd.append(open(fn).read().rstrip("\n"))
558
Tao Bao76def242017-11-21 09:25:31 -0800559 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700560 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700561 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700562
Tao Bao76def242017-11-21 09:25:31 -0800563 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000564 if args and args.strip():
565 cmd.extend(shlex.split(args))
566
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700567 if has_ramdisk:
568 cmd.extend(["--ramdisk", ramdisk_img.name])
569
Tao Baod95e9fd2015-03-29 23:07:41 -0700570 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800571 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700572 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700573 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700574 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700575 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700576
Tao Baobf70c312017-07-11 17:27:55 -0700577 # "boot" or "recovery", without extension.
578 partition_name = os.path.basename(sourcedir).lower()
579
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700580 if (partition_name == "recovery" and
581 info_dict.get("include_recovery_dtbo") == "true"):
582 fn = os.path.join(sourcedir, "recovery_dtbo")
583 cmd.extend(["--recovery_dtbo", fn])
584
Tao Bao986ee862018-10-04 15:46:16 -0700585 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700586
Tao Bao76def242017-11-21 09:25:31 -0800587 if (info_dict.get("boot_signer") == "true" and
588 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800589 # Hard-code the path as "/boot" for two-step special recovery image (which
590 # will be loaded into /boot during the two-step OTA).
591 if two_step_image:
592 path = "/boot"
593 else:
Tao Baobf70c312017-07-11 17:27:55 -0700594 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700595 cmd = [OPTIONS.boot_signer_path]
596 cmd.extend(OPTIONS.boot_signer_args)
597 cmd.extend([path, img.name,
598 info_dict["verity_key"] + ".pk8",
599 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700600 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700601
Tao Baod95e9fd2015-03-29 23:07:41 -0700602 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800603 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700604 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700605 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800606 # We have switched from the prebuilt futility binary to using the tool
607 # (futility-host) built from the source. Override the setting in the old
608 # TF.zip.
609 futility = info_dict["futility"]
610 if futility.startswith("prebuilts/"):
611 futility = "futility-host"
612 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700613 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700614 info_dict["vboot_key"] + ".vbprivk",
615 info_dict["vboot_subkey"] + ".vbprivk",
616 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700617 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700618 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700619
Tao Baof3282b42015-04-01 11:21:55 -0700620 # Clean up the temp files.
621 img_unsigned.close()
622 img_keyblock.close()
623
David Zeuthen8fecb282017-12-01 16:24:01 -0500624 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800625 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700626 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500627 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400628 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700629 "--partition_size", str(part_size), "--partition_name",
630 partition_name]
631 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500632 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400633 if args and args.strip():
634 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700635 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500636
637 img.seek(os.SEEK_SET, 0)
638 data = img.read()
639
640 if has_ramdisk:
641 ramdisk_img.close()
642 img.close()
643
644 return data
645
646
Doug Zongkerd5131602012-08-02 14:46:42 -0700647def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800648 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700649 """Return a File object with the desired bootable image.
650
651 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
652 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
653 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700654
Doug Zongker55d93282011-01-25 17:03:34 -0800655 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
656 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800657 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800658 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700659
660 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
661 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800662 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700663 return File.FromLocalFile(name, prebuilt_path)
664
Tao Bao89fbb0f2017-01-10 10:47:58 -0800665 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700666
667 if info_dict is None:
668 info_dict = OPTIONS.info_dict
669
670 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800671 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
672 # for recovery.
673 has_ramdisk = (info_dict.get("system_root_image") != "true" or
674 prebuilt_name != "boot.img" or
675 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700676
Doug Zongker6f1d0312014-08-22 08:07:12 -0700677 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400678 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
679 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800680 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700681 if data:
682 return File(name, data)
683 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800684
Doug Zongkereef39442009-04-02 12:14:19 -0700685
Narayan Kamatha07bf042017-08-14 14:49:21 +0100686def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800687 """Gunzips the given gzip compressed file to a given output file."""
688 with gzip.open(in_filename, "rb") as in_file, \
689 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100690 shutil.copyfileobj(in_file, out_file)
691
692
Doug Zongker75f17362009-12-08 13:46:44 -0800693def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800694 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800695
Tao Bao1c830bf2017-12-25 10:43:47 -0800696 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
697 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800698
Tao Bao1c830bf2017-12-25 10:43:47 -0800699 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800700 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800701 """
Doug Zongkereef39442009-04-02 12:14:19 -0700702
Doug Zongker55d93282011-01-25 17:03:34 -0800703 def unzip_to_dir(filename, dirname):
704 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
705 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800706 cmd.extend(pattern)
Tao Bao986ee862018-10-04 15:46:16 -0700707 RunAndCheckOutput(cmd)
Doug Zongker55d93282011-01-25 17:03:34 -0800708
Tao Bao1c830bf2017-12-25 10:43:47 -0800709 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800710 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
711 if m:
712 unzip_to_dir(m.group(1), tmp)
713 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
714 filename = m.group(1)
715 else:
716 unzip_to_dir(filename, tmp)
717
Tao Baodba59ee2018-01-09 13:21:02 -0800718 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700719
720
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700721def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
722 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800723 """Returns a SparseImage object suitable for passing to BlockImageDiff.
724
725 This function loads the specified sparse image from the given path, and
726 performs additional processing for OTA purpose. For example, it always adds
727 block 0 to clobbered blocks list. It also detects files that cannot be
728 reconstructed from the block list, for whom we should avoid applying imgdiff.
729
730 Args:
731 which: The partition name, which must be "system" or "vendor".
732 tmpdir: The directory that contains the prebuilt image and block map file.
733 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800734 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700735 hashtree_info_generator: If present, generates the hashtree_info for this
736 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800737 Returns:
738 A SparseImage object, with file_map info loaded.
739 """
740 assert which in ("system", "vendor")
741
742 path = os.path.join(tmpdir, "IMAGES", which + ".img")
743 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
744
745 # The image and map files must have been created prior to calling
746 # ota_from_target_files.py (since LMP).
747 assert os.path.exists(path) and os.path.exists(mappath)
748
749 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
750 # it to clobbered_blocks so that it will be written to the target
751 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
752 clobbered_blocks = "0"
753
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700754 image = sparse_img.SparseImage(
755 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
756 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800757
758 # block.map may contain less blocks, because mke2fs may skip allocating blocks
759 # if they contain all zeros. We can't reconstruct such a file from its block
760 # list. Tag such entries accordingly. (Bug: 65213616)
761 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800762 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700763 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800764 continue
765
Tom Cherryd14b8952018-08-09 14:26:00 -0700766 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
767 # filename listed in system.map may contain an additional leading slash
768 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
769 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700770 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
771
Tom Cherryd14b8952018-08-09 14:26:00 -0700772 # Special handling another case, where files not under /system
773 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700774 if which == 'system' and not arcname.startswith('SYSTEM'):
775 arcname = 'ROOT/' + arcname
776
777 assert arcname in input_zip.namelist(), \
778 "Failed to find the ZIP entry for {}".format(entry)
779
Tao Baoc765cca2018-01-31 17:32:40 -0800780 info = input_zip.getinfo(arcname)
781 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800782
783 # If a RangeSet has been tagged as using shared blocks while loading the
784 # image, its block list must be already incomplete due to that reason. Don't
785 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
786 if ranges.extra.get('uses_shared_blocks'):
787 continue
788
Tao Baoc765cca2018-01-31 17:32:40 -0800789 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
790 ranges.extra['incomplete'] = True
791
792 return image
793
794
Doug Zongkereef39442009-04-02 12:14:19 -0700795def GetKeyPasswords(keylist):
796 """Given a list of keys, prompt the user to enter passwords for
797 those which require them. Return a {key: password} dict. password
798 will be None if the key has no password."""
799
Doug Zongker8ce7c252009-05-22 13:34:54 -0700800 no_passwords = []
801 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700802 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700803 devnull = open("/dev/null", "w+b")
804 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800805 # We don't need a password for things that aren't really keys.
806 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700807 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700808 continue
809
T.R. Fullhart37e10522013-03-18 10:31:26 -0700810 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700811 "-inform", "DER", "-nocrypt"],
812 stdin=devnull.fileno(),
813 stdout=devnull.fileno(),
814 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700815 p.communicate()
816 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700817 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700818 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700819 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700820 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
821 "-inform", "DER", "-passin", "pass:"],
822 stdin=devnull.fileno(),
823 stdout=devnull.fileno(),
824 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700825 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700826 if p.returncode == 0:
827 # Encrypted key with empty string as password.
828 key_passwords[k] = ''
829 elif stderr.startswith('Error decrypting key'):
830 # Definitely encrypted key.
831 # It would have said "Error reading key" if it didn't parse correctly.
832 need_passwords.append(k)
833 else:
834 # Potentially, a type of key that openssl doesn't understand.
835 # We'll let the routines in signapk.jar handle it.
836 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700837 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700838
T.R. Fullhart37e10522013-03-18 10:31:26 -0700839 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800840 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700841 return key_passwords
842
843
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800844def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700845 """Gets the minSdkVersion declared in the APK.
846
847 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
848 This can be both a decimal number (API Level) or a codename.
849
850 Args:
851 apk_name: The APK filename.
852
853 Returns:
854 The parsed SDK version string.
855
856 Raises:
857 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800858 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700859 proc = Run(
860 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
861 stderr=subprocess.PIPE)
862 stdoutdata, stderrdata = proc.communicate()
863 if proc.returncode != 0:
864 raise ExternalError(
865 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
866 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800867
Tao Baof47bf0f2018-03-21 23:28:51 -0700868 for line in stdoutdata.split("\n"):
869 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800870 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
871 if m:
872 return m.group(1)
873 raise ExternalError("No minSdkVersion returned by aapt")
874
875
876def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700877 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800878
Tao Baof47bf0f2018-03-21 23:28:51 -0700879 If minSdkVersion is set to a codename, it is translated to a number using the
880 provided map.
881
882 Args:
883 apk_name: The APK filename.
884
885 Returns:
886 The parsed SDK version number.
887
888 Raises:
889 ExternalError: On failing to get the min SDK version number.
890 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800891 version = GetMinSdkVersion(apk_name)
892 try:
893 return int(version)
894 except ValueError:
895 # Not a decimal number. Codename?
896 if version in codename_to_api_level_map:
897 return codename_to_api_level_map[version]
898 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700899 raise ExternalError(
900 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
901 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800902
903
904def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800905 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700906 """Sign the input_name zip/jar/apk, producing output_name. Use the
907 given key and password (the latter may be None if the key does not
908 have a password.
909
Doug Zongker951495f2009-08-14 12:44:19 -0700910 If whole_file is true, use the "-w" option to SignApk to embed a
911 signature that covers the whole file in the archive comment of the
912 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800913
914 min_api_level is the API Level (int) of the oldest platform this file may end
915 up on. If not specified for an APK, the API Level is obtained by interpreting
916 the minSdkVersion attribute of the APK's AndroidManifest.xml.
917
918 codename_to_api_level_map is needed to translate the codename which may be
919 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700920 """
Tao Bao76def242017-11-21 09:25:31 -0800921 if codename_to_api_level_map is None:
922 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700923
Alex Klyubin9667b182015-12-10 13:38:50 -0800924 java_library_path = os.path.join(
925 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
926
Tao Baoe95540e2016-11-08 12:08:53 -0800927 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
928 ["-Djava.library.path=" + java_library_path,
929 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
930 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700931 if whole_file:
932 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800933
934 min_sdk_version = min_api_level
935 if min_sdk_version is None:
936 if not whole_file:
937 min_sdk_version = GetMinSdkVersionInt(
938 input_name, codename_to_api_level_map)
939 if min_sdk_version is not None:
940 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
941
T.R. Fullhart37e10522013-03-18 10:31:26 -0700942 cmd.extend([key + OPTIONS.public_key_suffix,
943 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800944 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700945
Tao Bao73dd4f42018-10-04 16:25:33 -0700946 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700947 if password is not None:
948 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -0700949 stdoutdata, _ = proc.communicate(password)
950 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700951 raise ExternalError(
952 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -0700953 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700954
Doug Zongkereef39442009-04-02 12:14:19 -0700955
Doug Zongker37974732010-09-16 17:44:38 -0700956def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800957 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700958
Tao Bao9dd909e2017-11-14 11:27:32 -0800959 For non-AVB images, raise exception if the data is too big. Print a warning
960 if the data is nearing the maximum size.
961
962 For AVB images, the actual image size should be identical to the limit.
963
964 Args:
965 data: A string that contains all the data for the partition.
966 target: The partition name. The ".img" suffix is optional.
967 info_dict: The dict to be looked up for relevant info.
968 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700969 if target.endswith(".img"):
970 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700971 mount_point = "/" + target
972
Ying Wangf8824af2014-06-03 14:07:27 -0700973 fs_type = None
974 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700975 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700976 if mount_point == "/userdata":
977 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700978 p = info_dict["fstab"][mount_point]
979 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800980 device = p.device
981 if "/" in device:
982 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -0800983 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -0700984 if not fs_type or not limit:
985 return
Doug Zongkereef39442009-04-02 12:14:19 -0700986
Andrew Boie0f9aec82012-02-14 09:32:52 -0800987 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800988 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
989 # path.
990 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
991 if size != limit:
992 raise ExternalError(
993 "Mismatching image size for %s: expected %d actual %d" % (
994 target, limit, size))
995 else:
996 pct = float(size) * 100.0 / limit
997 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
998 if pct >= 99.0:
999 raise ExternalError(msg)
1000 elif pct >= 95.0:
1001 print("\n WARNING: %s\n" % (msg,))
1002 elif OPTIONS.verbose:
1003 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001004
1005
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001006def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001007 """Parses the APK certs info from a given target-files zip.
1008
1009 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1010 tuple with the following elements: (1) a dictionary that maps packages to
1011 certs (based on the "certificate" and "private_key" attributes in the file;
1012 (2) a string representing the extension of compressed APKs in the target files
1013 (e.g ".gz", ".bro").
1014
1015 Args:
1016 tf_zip: The input target_files ZipFile (already open).
1017
1018 Returns:
1019 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1020 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1021 no compressed APKs.
1022 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001023 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001024 compressed_extension = None
1025
Tao Bao0f990332017-09-08 19:02:54 -07001026 # META/apkcerts.txt contains the info for _all_ the packages known at build
1027 # time. Filter out the ones that are not installed.
1028 installed_files = set()
1029 for name in tf_zip.namelist():
1030 basename = os.path.basename(name)
1031 if basename:
1032 installed_files.add(basename)
1033
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001034 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1035 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001036 if not line:
1037 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001038 m = re.match(
1039 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1040 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1041 line)
1042 if not m:
1043 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001044
Tao Bao818ddf52018-01-05 11:17:34 -08001045 matches = m.groupdict()
1046 cert = matches["CERT"]
1047 privkey = matches["PRIVKEY"]
1048 name = matches["NAME"]
1049 this_compressed_extension = matches["COMPRESSED"]
1050
1051 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1052 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1053 if cert in SPECIAL_CERT_STRINGS and not privkey:
1054 certmap[name] = cert
1055 elif (cert.endswith(OPTIONS.public_key_suffix) and
1056 privkey.endswith(OPTIONS.private_key_suffix) and
1057 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1058 certmap[name] = cert[:-public_key_suffix_len]
1059 else:
1060 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1061
1062 if not this_compressed_extension:
1063 continue
1064
1065 # Only count the installed files.
1066 filename = name + '.' + this_compressed_extension
1067 if filename not in installed_files:
1068 continue
1069
1070 # Make sure that all the values in the compression map have the same
1071 # extension. We don't support multiple compression methods in the same
1072 # system image.
1073 if compressed_extension:
1074 if this_compressed_extension != compressed_extension:
1075 raise ValueError(
1076 "Multiple compressed extensions: {} vs {}".format(
1077 compressed_extension, this_compressed_extension))
1078 else:
1079 compressed_extension = this_compressed_extension
1080
1081 return (certmap,
1082 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001083
1084
Doug Zongkereef39442009-04-02 12:14:19 -07001085COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001086Global options
1087
1088 -p (--path) <dir>
1089 Prepend <dir>/bin to the list of places to search for binaries run by this
1090 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001091
Doug Zongker05d3dea2009-06-22 11:32:31 -07001092 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001093 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001094
Tao Bao30df8b42018-04-23 15:32:53 -07001095 -x (--extra) <key=value>
1096 Add a key/value pair to the 'extras' dict, which device-specific extension
1097 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001098
Doug Zongkereef39442009-04-02 12:14:19 -07001099 -v (--verbose)
1100 Show command lines being executed.
1101
1102 -h (--help)
1103 Display this usage message and exit.
1104"""
1105
1106def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001107 print(docstring.rstrip("\n"))
1108 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001109
1110
1111def ParseOptions(argv,
1112 docstring,
1113 extra_opts="", extra_long_opts=(),
1114 extra_option_handler=None):
1115 """Parse the options in argv and return any arguments that aren't
1116 flags. docstring is the calling module's docstring, to be displayed
1117 for errors and -h. extra_opts and extra_long_opts are for flags
1118 defined by the caller, which are processed by passing them to
1119 extra_option_handler."""
1120
1121 try:
1122 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001123 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001124 ["help", "verbose", "path=", "signapk_path=",
1125 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001126 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001127 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1128 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001129 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001130 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001131 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001132 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001133 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001134 sys.exit(2)
1135
Doug Zongkereef39442009-04-02 12:14:19 -07001136 for o, a in opts:
1137 if o in ("-h", "--help"):
1138 Usage(docstring)
1139 sys.exit()
1140 elif o in ("-v", "--verbose"):
1141 OPTIONS.verbose = True
1142 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001143 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001144 elif o in ("--signapk_path",):
1145 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001146 elif o in ("--signapk_shared_library_path",):
1147 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001148 elif o in ("--extra_signapk_args",):
1149 OPTIONS.extra_signapk_args = shlex.split(a)
1150 elif o in ("--java_path",):
1151 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001152 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001153 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001154 elif o in ("--public_key_suffix",):
1155 OPTIONS.public_key_suffix = a
1156 elif o in ("--private_key_suffix",):
1157 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001158 elif o in ("--boot_signer_path",):
1159 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001160 elif o in ("--boot_signer_args",):
1161 OPTIONS.boot_signer_args = shlex.split(a)
1162 elif o in ("--verity_signer_path",):
1163 OPTIONS.verity_signer_path = a
1164 elif o in ("--verity_signer_args",):
1165 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001166 elif o in ("-s", "--device_specific"):
1167 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001168 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001169 key, value = a.split("=", 1)
1170 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001171 else:
1172 if extra_option_handler is None or not extra_option_handler(o, a):
1173 assert False, "unknown option \"%s\"" % (o,)
1174
Doug Zongker85448772014-09-09 14:59:20 -07001175 if OPTIONS.search_path:
1176 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1177 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001178
1179 return args
1180
1181
Tao Bao4c851b12016-09-19 13:54:38 -07001182def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001183 """Make a temp file and add it to the list of things to be deleted
1184 when Cleanup() is called. Return the filename."""
1185 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1186 os.close(fd)
1187 OPTIONS.tempfiles.append(fn)
1188 return fn
1189
1190
Tao Bao1c830bf2017-12-25 10:43:47 -08001191def MakeTempDir(prefix='tmp', suffix=''):
1192 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1193
1194 Returns:
1195 The absolute pathname of the new directory.
1196 """
1197 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1198 OPTIONS.tempfiles.append(dir_name)
1199 return dir_name
1200
1201
Doug Zongkereef39442009-04-02 12:14:19 -07001202def Cleanup():
1203 for i in OPTIONS.tempfiles:
1204 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001205 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001206 else:
1207 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001208 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001209
1210
1211class PasswordManager(object):
1212 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001213 self.editor = os.getenv("EDITOR")
1214 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001215
1216 def GetPasswords(self, items):
1217 """Get passwords corresponding to each string in 'items',
1218 returning a dict. (The dict may have keys in addition to the
1219 values in 'items'.)
1220
1221 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1222 user edit that file to add more needed passwords. If no editor is
1223 available, or $ANDROID_PW_FILE isn't define, prompts the user
1224 interactively in the ordinary way.
1225 """
1226
1227 current = self.ReadFile()
1228
1229 first = True
1230 while True:
1231 missing = []
1232 for i in items:
1233 if i not in current or not current[i]:
1234 missing.append(i)
1235 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001236 if not missing:
1237 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001238
1239 for i in missing:
1240 current[i] = ""
1241
1242 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001243 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001244 answer = raw_input("try to edit again? [y]> ").strip()
1245 if answer and answer[0] not in 'yY':
1246 raise RuntimeError("key passwords unavailable")
1247 first = False
1248
1249 current = self.UpdateAndReadFile(current)
1250
Dan Albert8b72aef2015-03-23 19:13:21 -07001251 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001252 """Prompt the user to enter a value (password) for each key in
1253 'current' whose value is fales. Returns a new dict with all the
1254 values.
1255 """
1256 result = {}
1257 for k, v in sorted(current.iteritems()):
1258 if v:
1259 result[k] = v
1260 else:
1261 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001262 result[k] = getpass.getpass(
1263 "Enter password for %s key> " % k).strip()
1264 if result[k]:
1265 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001266 return result
1267
1268 def UpdateAndReadFile(self, current):
1269 if not self.editor or not self.pwfile:
1270 return self.PromptResult(current)
1271
1272 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001273 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001274 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1275 f.write("# (Additional spaces are harmless.)\n\n")
1276
1277 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001278 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1279 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001280 f.write("[[[ %s ]]] %s\n" % (v, k))
1281 if not v and first_line is None:
1282 # position cursor on first line with no password.
1283 first_line = i + 4
1284 f.close()
1285
Tao Bao986ee862018-10-04 15:46:16 -07001286 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001287
1288 return self.ReadFile()
1289
1290 def ReadFile(self):
1291 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001292 if self.pwfile is None:
1293 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001294 try:
1295 f = open(self.pwfile, "r")
1296 for line in f:
1297 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001298 if not line or line[0] == '#':
1299 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001300 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1301 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001302 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001303 else:
1304 result[m.group(2)] = m.group(1)
1305 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001306 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001307 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001308 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001309 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001310
1311
Dan Albert8e0178d2015-01-27 15:53:15 -08001312def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1313 compress_type=None):
1314 import datetime
1315
1316 # http://b/18015246
1317 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1318 # for files larger than 2GiB. We can work around this by adjusting their
1319 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1320 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1321 # it isn't clear to me exactly what circumstances cause this).
1322 # `zipfile.write()` must be used directly to work around this.
1323 #
1324 # This mess can be avoided if we port to python3.
1325 saved_zip64_limit = zipfile.ZIP64_LIMIT
1326 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1327
1328 if compress_type is None:
1329 compress_type = zip_file.compression
1330 if arcname is None:
1331 arcname = filename
1332
1333 saved_stat = os.stat(filename)
1334
1335 try:
1336 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1337 # file to be zipped and reset it when we're done.
1338 os.chmod(filename, perms)
1339
1340 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001341 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1342 # intentional. zip stores datetimes in local time without a time zone
1343 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1344 # in the zip archive.
1345 local_epoch = datetime.datetime.fromtimestamp(0)
1346 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001347 os.utime(filename, (timestamp, timestamp))
1348
1349 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1350 finally:
1351 os.chmod(filename, saved_stat.st_mode)
1352 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1353 zipfile.ZIP64_LIMIT = saved_zip64_limit
1354
1355
Tao Bao58c1b962015-05-20 09:32:18 -07001356def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001357 compress_type=None):
1358 """Wrap zipfile.writestr() function to work around the zip64 limit.
1359
1360 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1361 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1362 when calling crc32(bytes).
1363
1364 But it still works fine to write a shorter string into a large zip file.
1365 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1366 when we know the string won't be too long.
1367 """
1368
1369 saved_zip64_limit = zipfile.ZIP64_LIMIT
1370 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1371
1372 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1373 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001374 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001375 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001376 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001377 else:
Tao Baof3282b42015-04-01 11:21:55 -07001378 zinfo = zinfo_or_arcname
1379
1380 # If compress_type is given, it overrides the value in zinfo.
1381 if compress_type is not None:
1382 zinfo.compress_type = compress_type
1383
Tao Bao58c1b962015-05-20 09:32:18 -07001384 # If perms is given, it has a priority.
1385 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001386 # If perms doesn't set the file type, mark it as a regular file.
1387 if perms & 0o770000 == 0:
1388 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001389 zinfo.external_attr = perms << 16
1390
Tao Baof3282b42015-04-01 11:21:55 -07001391 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001392 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1393
Dan Albert8b72aef2015-03-23 19:13:21 -07001394 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001395 zipfile.ZIP64_LIMIT = saved_zip64_limit
1396
1397
Tao Bao89d7ab22017-12-14 17:05:33 -08001398def ZipDelete(zip_filename, entries):
1399 """Deletes entries from a ZIP file.
1400
1401 Since deleting entries from a ZIP file is not supported, it shells out to
1402 'zip -d'.
1403
1404 Args:
1405 zip_filename: The name of the ZIP file.
1406 entries: The name of the entry, or the list of names to be deleted.
1407
1408 Raises:
1409 AssertionError: In case of non-zero return from 'zip'.
1410 """
1411 if isinstance(entries, basestring):
1412 entries = [entries]
1413 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001414 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001415
1416
Tao Baof3282b42015-04-01 11:21:55 -07001417def ZipClose(zip_file):
1418 # http://b/18015246
1419 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1420 # central directory.
1421 saved_zip64_limit = zipfile.ZIP64_LIMIT
1422 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1423
1424 zip_file.close()
1425
1426 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001427
1428
1429class DeviceSpecificParams(object):
1430 module = None
1431 def __init__(self, **kwargs):
1432 """Keyword arguments to the constructor become attributes of this
1433 object, which is passed to all functions in the device-specific
1434 module."""
1435 for k, v in kwargs.iteritems():
1436 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001437 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001438
1439 if self.module is None:
1440 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001441 if not path:
1442 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001443 try:
1444 if os.path.isdir(path):
1445 info = imp.find_module("releasetools", [path])
1446 else:
1447 d, f = os.path.split(path)
1448 b, x = os.path.splitext(f)
1449 if x == ".py":
1450 f = b
1451 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001452 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001453 self.module = imp.load_module("device_specific", *info)
1454 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001455 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001456
1457 def _DoCall(self, function_name, *args, **kwargs):
1458 """Call the named function in the device-specific module, passing
1459 the given args and kwargs. The first argument to the call will be
1460 the DeviceSpecific object itself. If there is no module, or the
1461 module does not define the function, return the value of the
1462 'default' kwarg (which itself defaults to None)."""
1463 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001464 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001465 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1466
1467 def FullOTA_Assertions(self):
1468 """Called after emitting the block of assertions at the top of a
1469 full OTA package. Implementations can add whatever additional
1470 assertions they like."""
1471 return self._DoCall("FullOTA_Assertions")
1472
Doug Zongkere5ff5902012-01-17 10:55:37 -08001473 def FullOTA_InstallBegin(self):
1474 """Called at the start of full OTA installation."""
1475 return self._DoCall("FullOTA_InstallBegin")
1476
Doug Zongker05d3dea2009-06-22 11:32:31 -07001477 def FullOTA_InstallEnd(self):
1478 """Called at the end of full OTA installation; typically this is
1479 used to install the image for the device's baseband processor."""
1480 return self._DoCall("FullOTA_InstallEnd")
1481
1482 def IncrementalOTA_Assertions(self):
1483 """Called after emitting the block of assertions at the top of an
1484 incremental OTA package. Implementations can add whatever
1485 additional assertions they like."""
1486 return self._DoCall("IncrementalOTA_Assertions")
1487
Doug Zongkere5ff5902012-01-17 10:55:37 -08001488 def IncrementalOTA_VerifyBegin(self):
1489 """Called at the start of the verification phase of incremental
1490 OTA installation; additional checks can be placed here to abort
1491 the script before any changes are made."""
1492 return self._DoCall("IncrementalOTA_VerifyBegin")
1493
Doug Zongker05d3dea2009-06-22 11:32:31 -07001494 def IncrementalOTA_VerifyEnd(self):
1495 """Called at the end of the verification phase of incremental OTA
1496 installation; additional checks can be placed here to abort the
1497 script before any changes are made."""
1498 return self._DoCall("IncrementalOTA_VerifyEnd")
1499
Doug Zongkere5ff5902012-01-17 10:55:37 -08001500 def IncrementalOTA_InstallBegin(self):
1501 """Called at the start of incremental OTA installation (after
1502 verification is complete)."""
1503 return self._DoCall("IncrementalOTA_InstallBegin")
1504
Doug Zongker05d3dea2009-06-22 11:32:31 -07001505 def IncrementalOTA_InstallEnd(self):
1506 """Called at the end of incremental OTA installation; typically
1507 this is used to install the image for the device's baseband
1508 processor."""
1509 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001510
Tao Bao9bc6bb22015-11-09 16:58:28 -08001511 def VerifyOTA_Assertions(self):
1512 return self._DoCall("VerifyOTA_Assertions")
1513
Tao Bao76def242017-11-21 09:25:31 -08001514
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001515class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001516 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001517 self.name = name
1518 self.data = data
1519 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001520 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001521 self.sha1 = sha1(data).hexdigest()
1522
1523 @classmethod
1524 def FromLocalFile(cls, name, diskname):
1525 f = open(diskname, "rb")
1526 data = f.read()
1527 f.close()
1528 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001529
1530 def WriteToTemp(self):
1531 t = tempfile.NamedTemporaryFile()
1532 t.write(self.data)
1533 t.flush()
1534 return t
1535
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001536 def WriteToDir(self, d):
1537 with open(os.path.join(d, self.name), "wb") as fp:
1538 fp.write(self.data)
1539
Geremy Condra36bd3652014-02-06 19:45:10 -08001540 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001541 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001542
Tao Bao76def242017-11-21 09:25:31 -08001543
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001544DIFF_PROGRAM_BY_EXT = {
1545 ".gz" : "imgdiff",
1546 ".zip" : ["imgdiff", "-z"],
1547 ".jar" : ["imgdiff", "-z"],
1548 ".apk" : ["imgdiff", "-z"],
1549 ".img" : "imgdiff",
1550 }
1551
Tao Bao76def242017-11-21 09:25:31 -08001552
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001553class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001554 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001555 self.tf = tf
1556 self.sf = sf
1557 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001558 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001559
1560 def ComputePatch(self):
1561 """Compute the patch (as a string of data) needed to turn sf into
1562 tf. Returns the same tuple as GetPatch()."""
1563
1564 tf = self.tf
1565 sf = self.sf
1566
Doug Zongker24cd2802012-08-14 16:36:15 -07001567 if self.diff_program:
1568 diff_program = self.diff_program
1569 else:
1570 ext = os.path.splitext(tf.name)[1]
1571 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001572
1573 ttemp = tf.WriteToTemp()
1574 stemp = sf.WriteToTemp()
1575
1576 ext = os.path.splitext(tf.name)[1]
1577
1578 try:
1579 ptemp = tempfile.NamedTemporaryFile()
1580 if isinstance(diff_program, list):
1581 cmd = copy.copy(diff_program)
1582 else:
1583 cmd = [diff_program]
1584 cmd.append(stemp.name)
1585 cmd.append(ttemp.name)
1586 cmd.append(ptemp.name)
1587 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001588 err = []
1589 def run():
1590 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001591 if e:
1592 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001593 th = threading.Thread(target=run)
1594 th.start()
1595 th.join(timeout=300) # 5 mins
1596 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001597 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001598 p.terminate()
1599 th.join(5)
1600 if th.is_alive():
1601 p.kill()
1602 th.join()
1603
Tianjie Xua2a9f992018-01-05 15:15:54 -08001604 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001605 print("WARNING: failure running %s:\n%s\n" % (
1606 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001607 self.patch = None
1608 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001609 diff = ptemp.read()
1610 finally:
1611 ptemp.close()
1612 stemp.close()
1613 ttemp.close()
1614
1615 self.patch = diff
1616 return self.tf, self.sf, self.patch
1617
1618
1619 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001620 """Returns a tuple of (target_file, source_file, patch_data).
1621
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001622 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001623 computing the patch failed.
1624 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001625 return self.tf, self.sf, self.patch
1626
1627
1628def ComputeDifferences(diffs):
1629 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001630 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001631
1632 # Do the largest files first, to try and reduce the long-pole effect.
1633 by_size = [(i.tf.size, i) for i in diffs]
1634 by_size.sort(reverse=True)
1635 by_size = [i[1] for i in by_size]
1636
1637 lock = threading.Lock()
1638 diff_iter = iter(by_size) # accessed under lock
1639
1640 def worker():
1641 try:
1642 lock.acquire()
1643 for d in diff_iter:
1644 lock.release()
1645 start = time.time()
1646 d.ComputePatch()
1647 dur = time.time() - start
1648 lock.acquire()
1649
1650 tf, sf, patch = d.GetPatch()
1651 if sf.name == tf.name:
1652 name = tf.name
1653 else:
1654 name = "%s (%s)" % (tf.name, sf.name)
1655 if patch is None:
Tao Bao76def242017-11-21 09:25:31 -08001656 print(
1657 "patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001658 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001659 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1660 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001661 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001662 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001663 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001664 raise
1665
1666 # start worker threads; wait for them all to finish.
1667 threads = [threading.Thread(target=worker)
1668 for i in range(OPTIONS.worker_threads)]
1669 for th in threads:
1670 th.start()
1671 while threads:
1672 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001673
1674
Dan Albert8b72aef2015-03-23 19:13:21 -07001675class BlockDifference(object):
1676 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001677 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001678 self.tgt = tgt
1679 self.src = src
1680 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001681 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001682 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001683
Tao Baodd2a5892015-03-12 12:32:37 -07001684 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001685 version = max(
1686 int(i) for i in
1687 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001688 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001689 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001690
1691 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001692 version=self.version,
1693 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001694 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001695 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001696 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001697 self.touched_src_ranges = b.touched_src_ranges
1698 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001699
Tao Baoaac4ad52015-10-16 15:26:34 -07001700 if src is None:
1701 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1702 else:
1703 _, self.device = GetTypeAndDevice("/" + partition,
1704 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001705
Tao Baod8d14be2016-02-04 14:26:02 -08001706 @property
1707 def required_cache(self):
1708 return self._required_cache
1709
Tao Bao76def242017-11-21 09:25:31 -08001710 def WriteScript(self, script, output_zip, progress=None,
1711 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001712 if not self.src:
1713 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001714 script.Print("Patching %s image unconditionally..." % (self.partition,))
1715 else:
1716 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001717
Dan Albert8b72aef2015-03-23 19:13:21 -07001718 if progress:
1719 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001720 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001721
1722 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001723 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001724
Tao Bao9bc6bb22015-11-09 16:58:28 -08001725 def WriteStrictVerifyScript(self, script):
1726 """Verify all the blocks in the care_map, including clobbered blocks.
1727
1728 This differs from the WriteVerifyScript() function: a) it prints different
1729 error messages; b) it doesn't allow half-way updated images to pass the
1730 verification."""
1731
1732 partition = self.partition
1733 script.Print("Verifying %s..." % (partition,))
1734 ranges = self.tgt.care_map
1735 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001736 script.AppendExtra(
1737 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1738 'ui_print("\\"%s\\" has unexpected contents.");' % (
1739 self.device, ranges_str,
1740 self.tgt.TotalSha1(include_clobbered_blocks=True),
1741 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001742 script.AppendExtra("")
1743
Tao Baod522bdc2016-04-12 15:53:16 -07001744 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001745 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001746
1747 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001748 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001749 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001750
1751 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001752 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001753 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001754 ranges = self.touched_src_ranges
1755 expected_sha1 = self.touched_src_sha1
1756 else:
1757 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1758 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001759
1760 # No blocks to be checked, skipping.
1761 if not ranges:
1762 return
1763
Tao Bao5ece99d2015-05-12 11:42:31 -07001764 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001765 script.AppendExtra(
1766 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1767 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1768 '"%s.patch.dat")) then' % (
1769 self.device, ranges_str, expected_sha1,
1770 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001771 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001772 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001773
Tianjie Xufc3422a2015-12-15 11:53:59 -08001774 if self.version >= 4:
1775
1776 # Bug: 21124327
1777 # When generating incrementals for the system and vendor partitions in
1778 # version 4 or newer, explicitly check the first block (which contains
1779 # the superblock) of the partition to see if it's what we expect. If
1780 # this check fails, give an explicit log message about the partition
1781 # having been remounted R/W (the most likely explanation).
1782 if self.check_first_block:
1783 script.AppendExtra('check_first_block("%s");' % (self.device,))
1784
1785 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001786 if partition == "system":
1787 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1788 else:
1789 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001790 script.AppendExtra((
1791 'ifelse (block_image_recover("{device}", "{ranges}") && '
1792 'block_image_verify("{device}", '
1793 'package_extract_file("{partition}.transfer.list"), '
1794 '"{partition}.new.dat", "{partition}.patch.dat"), '
1795 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001796 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001797 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001798 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001799
Tao Baodd2a5892015-03-12 12:32:37 -07001800 # Abort the OTA update. Note that the incremental OTA cannot be applied
1801 # even if it may match the checksum of the target partition.
1802 # a) If version < 3, operations like move and erase will make changes
1803 # unconditionally and damage the partition.
1804 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001805 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001806 if partition == "system":
1807 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1808 else:
1809 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1810 script.AppendExtra((
1811 'abort("E%d: %s partition has unexpected contents");\n'
1812 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001813
Tao Bao5fcaaef2015-06-01 13:40:49 -07001814 def _WritePostInstallVerifyScript(self, script):
1815 partition = self.partition
1816 script.Print('Verifying the updated %s image...' % (partition,))
1817 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1818 ranges = self.tgt.care_map
1819 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001820 script.AppendExtra(
1821 'if range_sha1("%s", "%s") == "%s" then' % (
1822 self.device, ranges_str,
1823 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001824
1825 # Bug: 20881595
1826 # Verify that extended blocks are really zeroed out.
1827 if self.tgt.extended:
1828 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001829 script.AppendExtra(
1830 'if range_sha1("%s", "%s") == "%s" then' % (
1831 self.device, ranges_str,
1832 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001833 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001834 if partition == "system":
1835 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1836 else:
1837 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001838 script.AppendExtra(
1839 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001840 ' abort("E%d: %s partition has unexpected non-zero contents after '
1841 'OTA update");\n'
1842 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001843 else:
1844 script.Print('Verified the updated %s image.' % (partition,))
1845
Tianjie Xu209db462016-05-24 17:34:52 -07001846 if partition == "system":
1847 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1848 else:
1849 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1850
Tao Bao5fcaaef2015-06-01 13:40:49 -07001851 script.AppendExtra(
1852 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001853 ' abort("E%d: %s partition has unexpected contents after OTA '
1854 'update");\n'
1855 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001856
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001857 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001858 ZipWrite(output_zip,
1859 '{}.transfer.list'.format(self.path),
1860 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001861
Tao Bao76def242017-11-21 09:25:31 -08001862 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1863 # its size. Quailty 9 almost triples the compression time but doesn't
1864 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001865 # zip | brotli(quality 6) | brotli(quality 9)
1866 # compressed_size: 942M | 869M (~8% reduced) | 854M
1867 # compression_time: 75s | 265s | 719s
1868 # decompression_time: 15s | 25s | 25s
1869
1870 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001871 brotli_cmd = ['brotli', '--quality=6',
1872 '--output={}.new.dat.br'.format(self.path),
1873 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001874 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07001875 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001876
1877 new_data_name = '{}.new.dat.br'.format(self.partition)
1878 ZipWrite(output_zip,
1879 '{}.new.dat.br'.format(self.path),
1880 new_data_name,
1881 compress_type=zipfile.ZIP_STORED)
1882 else:
1883 new_data_name = '{}.new.dat'.format(self.partition)
1884 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1885
Dan Albert8e0178d2015-01-27 15:53:15 -08001886 ZipWrite(output_zip,
1887 '{}.patch.dat'.format(self.path),
1888 '{}.patch.dat'.format(self.partition),
1889 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001890
Tianjie Xu209db462016-05-24 17:34:52 -07001891 if self.partition == "system":
1892 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1893 else:
1894 code = ErrorCode.VENDOR_UPDATE_FAILURE
1895
Dan Albert8e0178d2015-01-27 15:53:15 -08001896 call = ('block_image_update("{device}", '
1897 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001898 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001899 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001900 device=self.device, partition=self.partition,
1901 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001902 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001903
Dan Albert8b72aef2015-03-23 19:13:21 -07001904 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001905 data = source.ReadRangeSet(ranges)
1906 ctx = sha1()
1907
1908 for p in data:
1909 ctx.update(p)
1910
1911 return ctx.hexdigest()
1912
Tao Baoe9b61912015-07-09 17:37:49 -07001913 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1914 """Return the hash value for all zero blocks."""
1915 zero_block = '\x00' * 4096
1916 ctx = sha1()
1917 for _ in range(num_blocks):
1918 ctx.update(zero_block)
1919
1920 return ctx.hexdigest()
1921
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001922
1923DataImage = blockimgdiff.DataImage
1924
Tao Bao76def242017-11-21 09:25:31 -08001925
Doug Zongker96a57e72010-09-26 14:57:41 -07001926# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001927PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001928 "ext4": "EMMC",
1929 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001930 "f2fs": "EMMC",
1931 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001932}
Doug Zongker96a57e72010-09-26 14:57:41 -07001933
Tao Bao76def242017-11-21 09:25:31 -08001934
Doug Zongker96a57e72010-09-26 14:57:41 -07001935def GetTypeAndDevice(mount_point, info):
1936 fstab = info["fstab"]
1937 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001938 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1939 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001940 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001941 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001942
1943
1944def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001945 """Parses and converts a PEM-encoded certificate into DER-encoded.
1946
1947 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1948
1949 Returns:
1950 The decoded certificate string.
1951 """
1952 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001953 save = False
1954 for line in data.split("\n"):
1955 if "--END CERTIFICATE--" in line:
1956 break
1957 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001958 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001959 if "--BEGIN CERTIFICATE--" in line:
1960 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001961 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001962 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001963
Tao Bao04e1f012018-02-04 12:13:35 -08001964
1965def ExtractPublicKey(cert):
1966 """Extracts the public key (PEM-encoded) from the given certificate file.
1967
1968 Args:
1969 cert: The certificate filename.
1970
1971 Returns:
1972 The public key string.
1973
1974 Raises:
1975 AssertionError: On non-zero return from 'openssl'.
1976 """
1977 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1978 # While openssl 1.1 writes the key into the given filename followed by '-out',
1979 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1980 # stdout instead.
1981 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1982 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1983 pubkey, stderrdata = proc.communicate()
1984 assert proc.returncode == 0, \
1985 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1986 return pubkey
1987
1988
Doug Zongker412c02f2014-02-13 10:58:24 -08001989def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1990 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001991 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001992
Tao Bao6d5d6232018-03-09 17:04:42 -08001993 Most of the space in the boot and recovery images is just the kernel, which is
1994 identical for the two, so the resulting patch should be efficient. Add it to
1995 the output zip, along with a shell script that is run from init.rc on first
1996 boot to actually do the patching and install the new recovery image.
1997
1998 Args:
1999 input_dir: The top-level input directory of the target-files.zip.
2000 output_sink: The callback function that writes the result.
2001 recovery_img: File object for the recovery image.
2002 boot_img: File objects for the boot image.
2003 info_dict: A dict returned by common.LoadInfoDict() on the input
2004 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002005 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002006 if info_dict is None:
2007 info_dict = OPTIONS.info_dict
2008
Tao Bao6d5d6232018-03-09 17:04:42 -08002009 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002010
Tao Baof2cffbd2015-07-22 12:33:18 -07002011 if full_recovery_image:
2012 output_sink("etc/recovery.img", recovery_img.data)
2013
2014 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002015 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002016 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002017 # With system-root-image, boot and recovery images will have mismatching
2018 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2019 # to handle such a case.
2020 if system_root_image:
2021 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002022 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002023 assert not os.path.exists(path)
2024 else:
2025 diff_program = ["imgdiff"]
2026 if os.path.exists(path):
2027 diff_program.append("-b")
2028 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002029 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002030 else:
2031 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002032
2033 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2034 _, _, patch = d.ComputePatch()
2035 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002036
Dan Albertebb19aa2015-03-27 19:11:53 -07002037 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002038 # The following GetTypeAndDevice()s need to use the path in the target
2039 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002040 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2041 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2042 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002043 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002044
Tao Baof2cffbd2015-07-22 12:33:18 -07002045 if full_recovery_image:
2046 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002047if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2048 applypatch \\
2049 --flash /system/etc/recovery.img \\
2050 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2051 log -t recovery "Installing new recovery image: succeeded" || \\
2052 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002053else
2054 log -t recovery "Recovery image already installed"
2055fi
2056""" % {'type': recovery_type,
2057 'device': recovery_device,
2058 'sha1': recovery_img.sha1,
2059 'size': recovery_img.size}
2060 else:
2061 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002062if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2063 applypatch %(bonus_args)s \\
2064 --patch /system/recovery-from-boot.p \\
2065 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2066 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2067 log -t recovery "Installing new recovery image: succeeded" || \\
2068 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002069else
2070 log -t recovery "Recovery image already installed"
2071fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002072""" % {'boot_size': boot_img.size,
2073 'boot_sha1': boot_img.sha1,
2074 'recovery_size': recovery_img.size,
2075 'recovery_sha1': recovery_img.sha1,
2076 'boot_type': boot_type,
2077 'boot_device': boot_device,
2078 'recovery_type': recovery_type,
2079 'recovery_device': recovery_device,
2080 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002081
2082 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002083 # in the L release.
2084 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002085
Tao Bao89fbb0f2017-01-10 10:47:58 -08002086 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002087
2088 output_sink(sh_location, sh)