blob: 7cca7663f58a3792bd67f03b665d0f7e45109448 [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
Tao Bao71197512018-10-11 14:08:45 -070076# The block size that's used across the releasetools scripts.
77BLOCK_SIZE = 4096
78
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080079# Values for "certificate" in apkcerts that mean special things.
80SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
81
Tao Bao9dd909e2017-11-14 11:27:32 -080082# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010083AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010084 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080085
Tianjie Xu861f4132018-09-12 11:49:33 -070086# Partitions that should have their care_map added to META/care_map.pb
87PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
88 'odm')
89
90
Tianjie Xu209db462016-05-24 17:34:52 -070091class ErrorCode(object):
92 """Define error_codes for failures that happen during the actual
93 update package installation.
94
95 Error codes 0-999 are reserved for failures before the package
96 installation (i.e. low battery, package verification failure).
97 Detailed code in 'bootable/recovery/error_code.h' """
98
99 SYSTEM_VERIFICATION_FAILURE = 1000
100 SYSTEM_UPDATE_FAILURE = 1001
101 SYSTEM_UNEXPECTED_CONTENTS = 1002
102 SYSTEM_NONZERO_CONTENTS = 1003
103 SYSTEM_RECOVER_FAILURE = 1004
104 VENDOR_VERIFICATION_FAILURE = 2000
105 VENDOR_UPDATE_FAILURE = 2001
106 VENDOR_UNEXPECTED_CONTENTS = 2002
107 VENDOR_NONZERO_CONTENTS = 2003
108 VENDOR_RECOVER_FAILURE = 2004
109 OEM_PROP_MISMATCH = 3000
110 FINGERPRINT_MISMATCH = 3001
111 THUMBPRINT_MISMATCH = 3002
112 OLDER_BUILD = 3003
113 DEVICE_MISMATCH = 3004
114 BAD_PATCH_FILE = 3005
115 INSUFFICIENT_CACHE_SPACE = 3006
116 TUNE_PARTITION_FAILURE = 3007
117 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800118
Tao Bao80921982018-03-21 21:02:19 -0700119
Dan Albert8b72aef2015-03-23 19:13:21 -0700120class ExternalError(RuntimeError):
121 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700122
123
Tao Bao39451582017-05-04 11:10:47 -0700124def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700125 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700126
Tao Bao73dd4f42018-10-04 16:25:33 -0700127 Args:
128 args: The command represented as a list of strings.
129 verbose: Whether the commands should be shown (default to OPTIONS.verbose
130 if unspecified).
131 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
132 stdin, etc. stdout and stderr will default to subprocess.PIPE and
133 subprocess.STDOUT respectively unless caller specifies any of them.
134
135 Returns:
136 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700137 """
138 if verbose is None:
139 verbose = OPTIONS.verbose
Tao Bao73dd4f42018-10-04 16:25:33 -0700140 if 'stdout' not in kwargs and 'stderr' not in kwargs:
141 kwargs['stdout'] = subprocess.PIPE
142 kwargs['stderr'] = subprocess.STDOUT
Tao Bao39451582017-05-04 11:10:47 -0700143 if verbose:
Tao Bao73dd4f42018-10-04 16:25:33 -0700144 print(" Running: \"{}\"".format(" ".join(args)))
Doug Zongkereef39442009-04-02 12:14:19 -0700145 return subprocess.Popen(args, **kwargs)
146
147
Tao Bao986ee862018-10-04 15:46:16 -0700148def RunAndCheckOutput(args, verbose=None, **kwargs):
149 """Runs the given command and returns the output.
150
151 Args:
152 args: The command represented as a list of strings.
153 verbose: Whether the commands should be shown (default to OPTIONS.verbose
154 if unspecified).
155 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
156 stdin, etc. stdout and stderr will default to subprocess.PIPE and
157 subprocess.STDOUT respectively unless caller specifies any of them.
158
159 Returns:
160 The output string.
161
162 Raises:
163 ExternalError: On non-zero exit from the command.
164 """
165 if verbose is None:
166 verbose = OPTIONS.verbose
167 proc = Run(args, verbose=verbose, **kwargs)
168 output, _ = proc.communicate()
169 if verbose:
170 print("{}".format(output.rstrip()))
171 if proc.returncode != 0:
172 raise ExternalError(
173 "Failed to run command '{}' (exit code {}):\n{}".format(
174 args, proc.returncode, output))
175 return output
176
177
Tao Baoc765cca2018-01-31 17:32:40 -0800178def RoundUpTo4K(value):
179 rounded_up = value + 4095
180 return rounded_up - (rounded_up % 4096)
181
182
Ying Wang7e6d4e42010-12-13 16:25:36 -0800183def CloseInheritedPipes():
184 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
185 before doing other work."""
186 if platform.system() != "Darwin":
187 return
188 for d in range(3, 1025):
189 try:
190 stat = os.fstat(d)
191 if stat is not None:
192 pipebit = stat[0] & 0x1000
193 if pipebit != 0:
194 os.close(d)
195 except OSError:
196 pass
197
198
Tao Bao410ad8b2018-08-24 12:08:38 -0700199def LoadInfoDict(input_file, repacking=False):
200 """Loads the key/value pairs from the given input target_files.
201
202 It reads `META/misc_info.txt` file in the target_files input, does sanity
203 checks and returns the parsed key/value pairs for to the given build. It's
204 usually called early when working on input target_files files, e.g. when
205 generating OTAs, or signing builds. Note that the function may be called
206 against an old target_files file (i.e. from past dessert releases). So the
207 property parsing needs to be backward compatible.
208
209 In a `META/misc_info.txt`, a few properties are stored as links to the files
210 in the PRODUCT_OUT directory. It works fine with the build system. However,
211 they are no longer available when (re)generating images from target_files zip.
212 When `repacking` is True, redirect these properties to the actual files in the
213 unzipped directory.
214
215 Args:
216 input_file: The input target_files file, which could be an open
217 zipfile.ZipFile instance, or a str for the dir that contains the files
218 unzipped from a target_files file.
219 repacking: Whether it's trying repack an target_files file after loading the
220 info dict (default: False). If so, it will rewrite a few loaded
221 properties (e.g. selinux_fc, root_dir) to point to the actual files in
222 target_files file. When doing repacking, `input_file` must be a dir.
223
224 Returns:
225 A dict that contains the parsed key/value pairs.
226
227 Raises:
228 AssertionError: On invalid input arguments.
229 ValueError: On malformed input values.
230 """
231 if repacking:
232 assert isinstance(input_file, str), \
233 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700234
Doug Zongkerc9253822014-02-04 12:17:58 -0800235 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700236 if isinstance(input_file, zipfile.ZipFile):
237 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800238 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700239 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800240 try:
241 with open(path) as f:
242 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700243 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800244 if e.errno == errno.ENOENT:
245 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800246
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700247 try:
Michael Runge6e836112014-04-15 17:40:21 -0700248 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700249 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700250 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700251
Tao Bao410ad8b2018-08-24 12:08:38 -0700252 if "recovery_api_version" not in d:
253 raise ValueError("Failed to find 'recovery_api_version'")
254 if "fstab_version" not in d:
255 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800256
Tao Bao410ad8b2018-08-24 12:08:38 -0700257 if repacking:
258 # We carry a copy of file_contexts.bin under META/. If not available, search
259 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
260 # images than the one running on device, in that case, we must have the one
261 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700262 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700263 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700264 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700265
Tom Cherryd14b8952018-08-09 14:26:00 -0700266 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700267
Tom Cherryd14b8952018-08-09 14:26:00 -0700268 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700269 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700270 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700271 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700272
Tao Baof54216f2016-03-29 15:12:37 -0700273 # Redirect {system,vendor}_base_fs_file.
274 if "system_base_fs_file" in d:
275 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700276 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700277 if os.path.exists(system_base_fs_file):
278 d["system_base_fs_file"] = system_base_fs_file
279 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800280 print("Warning: failed to find system base fs file: %s" % (
281 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700282 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700283
284 if "vendor_base_fs_file" in d:
285 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700286 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700287 if os.path.exists(vendor_base_fs_file):
288 d["vendor_base_fs_file"] = vendor_base_fs_file
289 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800290 print("Warning: failed to find vendor base fs file: %s" % (
291 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700292 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700293
Doug Zongker37974732010-09-16 17:44:38 -0700294 def makeint(key):
295 if key in d:
296 d[key] = int(d[key], 0)
297
298 makeint("recovery_api_version")
299 makeint("blocksize")
300 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700301 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700302 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700303 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700304 makeint("recovery_size")
305 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800306 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700307
Tao Baoa57ab9f2018-08-24 12:08:38 -0700308 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
309 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
310 # cases, since it may load the info_dict from an old build (e.g. when
311 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800312 system_root_image = d.get("system_root_image") == "true"
313 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700314 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700315 if isinstance(input_file, zipfile.ZipFile):
316 if recovery_fstab_path not in input_file.namelist():
317 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
318 else:
319 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
320 if not os.path.exists(path):
321 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800322 d["fstab"] = LoadRecoveryFSTab(
323 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700324
Tao Bao76def242017-11-21 09:25:31 -0800325 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700326 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700327 if isinstance(input_file, zipfile.ZipFile):
328 if recovery_fstab_path not in input_file.namelist():
329 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
330 else:
331 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
332 if not os.path.exists(path):
333 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800334 d["fstab"] = LoadRecoveryFSTab(
335 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700336
Tianjie Xucfa86222016-03-07 16:31:19 -0800337 else:
338 d["fstab"] = None
339
Tianjie Xu861f4132018-09-12 11:49:33 -0700340 # Tries to load the build props for all partitions with care_map, including
341 # system and vendor.
342 for partition in PARTITIONS_WITH_CARE_MAP:
343 d["{}.build.prop".format(partition)] = LoadBuildProp(
344 read_helper, "{}/build.prop".format(partition.upper()))
345 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800346
347 # Set up the salt (based on fingerprint or thumbprint) that will be used when
348 # adding AVB footer.
349 if d.get("avb_enable") == "true":
350 fp = None
351 if "build.prop" in d:
352 build_prop = d["build.prop"]
353 if "ro.build.fingerprint" in build_prop:
354 fp = build_prop["ro.build.fingerprint"]
355 elif "ro.build.thumbprint" in build_prop:
356 fp = build_prop["ro.build.thumbprint"]
357 if fp:
358 d["avb_salt"] = sha256(fp).hexdigest()
359
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700360 return d
361
Tao Baod1de6f32017-03-01 16:38:48 -0800362
Tao Baobcd1d162017-08-26 13:10:26 -0700363def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700364 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700365 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700366 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700367 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700368 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700369 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700370
Tao Baod1de6f32017-03-01 16:38:48 -0800371
Michael Runge6e836112014-04-15 17:40:21 -0700372def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700373 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700374 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700375 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700376 if not line or line.startswith("#"):
377 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700378 if "=" in line:
379 name, value = line.split("=", 1)
380 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700381 return d
382
Tao Baod1de6f32017-03-01 16:38:48 -0800383
Tianjie Xucfa86222016-03-07 16:31:19 -0800384def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
385 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700386 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800387 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700388 self.mount_point = mount_point
389 self.fs_type = fs_type
390 self.device = device
391 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700392 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700393
394 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800395 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700396 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800397 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700398 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700399
Tao Baod1de6f32017-03-01 16:38:48 -0800400 assert fstab_version == 2
401
402 d = {}
403 for line in data.split("\n"):
404 line = line.strip()
405 if not line or line.startswith("#"):
406 continue
407
408 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
409 pieces = line.split()
410 if len(pieces) != 5:
411 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
412
413 # Ignore entries that are managed by vold.
414 options = pieces[4]
415 if "voldmanaged=" in options:
416 continue
417
418 # It's a good line, parse it.
419 length = 0
420 options = options.split(",")
421 for i in options:
422 if i.startswith("length="):
423 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800424 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800425 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700426 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800427
Tao Baod1de6f32017-03-01 16:38:48 -0800428 mount_flags = pieces[3]
429 # Honor the SELinux context if present.
430 context = None
431 for i in mount_flags.split(","):
432 if i.startswith("context="):
433 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800434
Tao Baod1de6f32017-03-01 16:38:48 -0800435 mount_point = pieces[1]
436 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
437 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800438
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700439 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700440 # system. Other areas assume system is always at "/system" so point /system
441 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700442 if system_root_image:
443 assert not d.has_key("/system") and d.has_key("/")
444 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700445 return d
446
447
Doug Zongker37974732010-09-16 17:44:38 -0700448def DumpInfoDict(d):
449 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800450 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700451
Dan Albert8b72aef2015-03-23 19:13:21 -0700452
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800453def AppendAVBSigningArgs(cmd, partition):
454 """Append signing arguments for avbtool."""
455 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
456 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
457 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
458 if key_path and algorithm:
459 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700460 avb_salt = OPTIONS.info_dict.get("avb_salt")
461 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700462 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700463 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800464
465
Tao Bao02a08592018-07-22 12:40:45 -0700466def GetAvbChainedPartitionArg(partition, info_dict, key=None):
467 """Constructs and returns the arg to build or verify a chained partition.
468
469 Args:
470 partition: The partition name.
471 info_dict: The info dict to look up the key info and rollback index
472 location.
473 key: The key to be used for building or verifying the partition. Defaults to
474 the key listed in info_dict.
475
476 Returns:
477 A string of form "partition:rollback_index_location:key" that can be used to
478 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700479 """
480 if key is None:
481 key = info_dict["avb_" + partition + "_key_path"]
482 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
483 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
Tao Bao986ee862018-10-04 15:46:16 -0700484 RunAndCheckOutput(
Tao Bao73dd4f42018-10-04 16:25:33 -0700485 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path])
Tao Bao02a08592018-07-22 12:40:45 -0700486
487 rollback_index_location = info_dict[
488 "avb_" + partition + "_rollback_index_location"]
489 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
490
491
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700492def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800493 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700494 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700495
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700496 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800497 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
498 we are building a two-step special image (i.e. building a recovery image to
499 be loaded into /boot in two-step OTAs).
500
501 Return the image data, or None if sourcedir does not appear to contains files
502 for building the requested image.
503 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700504
505 def make_ramdisk():
506 ramdisk_img = tempfile.NamedTemporaryFile()
507
508 if os.access(fs_config_file, os.F_OK):
509 cmd = ["mkbootfs", "-f", fs_config_file,
510 os.path.join(sourcedir, "RAMDISK")]
511 else:
512 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
513 p1 = Run(cmd, stdout=subprocess.PIPE)
514 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
515
516 p2.wait()
517 p1.wait()
518 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
519 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
520
521 return ramdisk_img
522
523 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
524 return None
525
526 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700527 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700528
Doug Zongkerd5131602012-08-02 14:46:42 -0700529 if info_dict is None:
530 info_dict = OPTIONS.info_dict
531
Doug Zongkereef39442009-04-02 12:14:19 -0700532 img = tempfile.NamedTemporaryFile()
533
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700534 if has_ramdisk:
535 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700536
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800537 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
538 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
539
540 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700541
Benoit Fradina45a8682014-07-14 21:00:43 +0200542 fn = os.path.join(sourcedir, "second")
543 if os.access(fn, os.F_OK):
544 cmd.append("--second")
545 cmd.append(fn)
546
Doug Zongker171f1cd2009-06-15 22:36:37 -0700547 fn = os.path.join(sourcedir, "cmdline")
548 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700549 cmd.append("--cmdline")
550 cmd.append(open(fn).read().rstrip("\n"))
551
552 fn = os.path.join(sourcedir, "base")
553 if os.access(fn, os.F_OK):
554 cmd.append("--base")
555 cmd.append(open(fn).read().rstrip("\n"))
556
Ying Wang4de6b5b2010-08-25 14:29:34 -0700557 fn = os.path.join(sourcedir, "pagesize")
558 if os.access(fn, os.F_OK):
559 cmd.append("--pagesize")
560 cmd.append(open(fn).read().rstrip("\n"))
561
Tao Bao76def242017-11-21 09:25:31 -0800562 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700563 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700564 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700565
Tao Bao76def242017-11-21 09:25:31 -0800566 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000567 if args and args.strip():
568 cmd.extend(shlex.split(args))
569
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700570 if has_ramdisk:
571 cmd.extend(["--ramdisk", ramdisk_img.name])
572
Tao Baod95e9fd2015-03-29 23:07:41 -0700573 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800574 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700575 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700576 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700577 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700578 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700579
Tao Baobf70c3182017-07-11 17:27:55 -0700580 # "boot" or "recovery", without extension.
581 partition_name = os.path.basename(sourcedir).lower()
582
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700583 if (partition_name == "recovery" and
584 info_dict.get("include_recovery_dtbo") == "true"):
585 fn = os.path.join(sourcedir, "recovery_dtbo")
586 cmd.extend(["--recovery_dtbo", fn])
587
Tao Bao986ee862018-10-04 15:46:16 -0700588 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700589
Tao Bao76def242017-11-21 09:25:31 -0800590 if (info_dict.get("boot_signer") == "true" and
591 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800592 # Hard-code the path as "/boot" for two-step special recovery image (which
593 # will be loaded into /boot during the two-step OTA).
594 if two_step_image:
595 path = "/boot"
596 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700597 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700598 cmd = [OPTIONS.boot_signer_path]
599 cmd.extend(OPTIONS.boot_signer_args)
600 cmd.extend([path, img.name,
601 info_dict["verity_key"] + ".pk8",
602 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700603 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700604
Tao Baod95e9fd2015-03-29 23:07:41 -0700605 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800606 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700607 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700608 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800609 # We have switched from the prebuilt futility binary to using the tool
610 # (futility-host) built from the source. Override the setting in the old
611 # TF.zip.
612 futility = info_dict["futility"]
613 if futility.startswith("prebuilts/"):
614 futility = "futility-host"
615 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700616 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700617 info_dict["vboot_key"] + ".vbprivk",
618 info_dict["vboot_subkey"] + ".vbprivk",
619 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700620 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700621 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700622
Tao Baof3282b42015-04-01 11:21:55 -0700623 # Clean up the temp files.
624 img_unsigned.close()
625 img_keyblock.close()
626
David Zeuthen8fecb282017-12-01 16:24:01 -0500627 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800628 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700629 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500630 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400631 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700632 "--partition_size", str(part_size), "--partition_name",
633 partition_name]
634 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500635 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400636 if args and args.strip():
637 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700638 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500639
640 img.seek(os.SEEK_SET, 0)
641 data = img.read()
642
643 if has_ramdisk:
644 ramdisk_img.close()
645 img.close()
646
647 return data
648
649
Doug Zongkerd5131602012-08-02 14:46:42 -0700650def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800651 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700652 """Return a File object with the desired bootable image.
653
654 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
655 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
656 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700657
Doug Zongker55d93282011-01-25 17:03:34 -0800658 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
659 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800660 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800661 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700662
663 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
664 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800665 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700666 return File.FromLocalFile(name, prebuilt_path)
667
Tao Bao89fbb0f2017-01-10 10:47:58 -0800668 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700669
670 if info_dict is None:
671 info_dict = OPTIONS.info_dict
672
673 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800674 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
675 # for recovery.
676 has_ramdisk = (info_dict.get("system_root_image") != "true" or
677 prebuilt_name != "boot.img" or
678 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700679
Doug Zongker6f1d0312014-08-22 08:07:12 -0700680 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400681 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
682 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800683 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700684 if data:
685 return File(name, data)
686 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800687
Doug Zongkereef39442009-04-02 12:14:19 -0700688
Narayan Kamatha07bf042017-08-14 14:49:21 +0100689def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800690 """Gunzips the given gzip compressed file to a given output file."""
691 with gzip.open(in_filename, "rb") as in_file, \
692 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100693 shutil.copyfileobj(in_file, out_file)
694
695
Doug Zongker75f17362009-12-08 13:46:44 -0800696def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800697 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800698
Tao Bao1c830bf2017-12-25 10:43:47 -0800699 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
700 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800701
Tao Bao1c830bf2017-12-25 10:43:47 -0800702 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800703 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800704 """
Doug Zongkereef39442009-04-02 12:14:19 -0700705
Doug Zongker55d93282011-01-25 17:03:34 -0800706 def unzip_to_dir(filename, dirname):
707 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
708 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800709 cmd.extend(pattern)
Tao Bao986ee862018-10-04 15:46:16 -0700710 RunAndCheckOutput(cmd)
Doug Zongker55d93282011-01-25 17:03:34 -0800711
Tao Bao1c830bf2017-12-25 10:43:47 -0800712 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800713 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
714 if m:
715 unzip_to_dir(m.group(1), tmp)
716 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
717 filename = m.group(1)
718 else:
719 unzip_to_dir(filename, tmp)
720
Tao Baodba59ee2018-01-09 13:21:02 -0800721 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700722
723
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700724def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
725 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800726 """Returns a SparseImage object suitable for passing to BlockImageDiff.
727
728 This function loads the specified sparse image from the given path, and
729 performs additional processing for OTA purpose. For example, it always adds
730 block 0 to clobbered blocks list. It also detects files that cannot be
731 reconstructed from the block list, for whom we should avoid applying imgdiff.
732
733 Args:
734 which: The partition name, which must be "system" or "vendor".
735 tmpdir: The directory that contains the prebuilt image and block map file.
736 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800737 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700738 hashtree_info_generator: If present, generates the hashtree_info for this
739 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800740 Returns:
741 A SparseImage object, with file_map info loaded.
742 """
743 assert which in ("system", "vendor")
744
745 path = os.path.join(tmpdir, "IMAGES", which + ".img")
746 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
747
748 # The image and map files must have been created prior to calling
749 # ota_from_target_files.py (since LMP).
750 assert os.path.exists(path) and os.path.exists(mappath)
751
752 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
753 # it to clobbered_blocks so that it will be written to the target
754 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
755 clobbered_blocks = "0"
756
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700757 image = sparse_img.SparseImage(
758 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
759 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800760
761 # block.map may contain less blocks, because mke2fs may skip allocating blocks
762 # if they contain all zeros. We can't reconstruct such a file from its block
763 # list. Tag such entries accordingly. (Bug: 65213616)
764 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800765 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700766 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800767 continue
768
Tom Cherryd14b8952018-08-09 14:26:00 -0700769 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
770 # filename listed in system.map may contain an additional leading slash
771 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
772 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700773 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
774
Tom Cherryd14b8952018-08-09 14:26:00 -0700775 # Special handling another case, where files not under /system
776 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700777 if which == 'system' and not arcname.startswith('SYSTEM'):
778 arcname = 'ROOT/' + arcname
779
780 assert arcname in input_zip.namelist(), \
781 "Failed to find the ZIP entry for {}".format(entry)
782
Tao Baoc765cca2018-01-31 17:32:40 -0800783 info = input_zip.getinfo(arcname)
784 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800785
786 # If a RangeSet has been tagged as using shared blocks while loading the
787 # image, its block list must be already incomplete due to that reason. Don't
788 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
789 if ranges.extra.get('uses_shared_blocks'):
790 continue
791
Tao Baoc765cca2018-01-31 17:32:40 -0800792 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
793 ranges.extra['incomplete'] = True
794
795 return image
796
797
Doug Zongkereef39442009-04-02 12:14:19 -0700798def GetKeyPasswords(keylist):
799 """Given a list of keys, prompt the user to enter passwords for
800 those which require them. Return a {key: password} dict. password
801 will be None if the key has no password."""
802
Doug Zongker8ce7c252009-05-22 13:34:54 -0700803 no_passwords = []
804 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700805 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700806 devnull = open("/dev/null", "w+b")
807 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800808 # We don't need a password for things that aren't really keys.
809 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700810 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700811 continue
812
T.R. Fullhart37e10522013-03-18 10:31:26 -0700813 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700814 "-inform", "DER", "-nocrypt"],
815 stdin=devnull.fileno(),
816 stdout=devnull.fileno(),
817 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700818 p.communicate()
819 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700820 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700821 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700822 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700823 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
824 "-inform", "DER", "-passin", "pass:"],
825 stdin=devnull.fileno(),
826 stdout=devnull.fileno(),
827 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700828 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700829 if p.returncode == 0:
830 # Encrypted key with empty string as password.
831 key_passwords[k] = ''
832 elif stderr.startswith('Error decrypting key'):
833 # Definitely encrypted key.
834 # It would have said "Error reading key" if it didn't parse correctly.
835 need_passwords.append(k)
836 else:
837 # Potentially, a type of key that openssl doesn't understand.
838 # We'll let the routines in signapk.jar handle it.
839 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700840 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700841
T.R. Fullhart37e10522013-03-18 10:31:26 -0700842 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800843 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700844 return key_passwords
845
846
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800847def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700848 """Gets the minSdkVersion declared in the APK.
849
850 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
851 This can be both a decimal number (API Level) or a codename.
852
853 Args:
854 apk_name: The APK filename.
855
856 Returns:
857 The parsed SDK version string.
858
859 Raises:
860 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800861 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700862 proc = Run(
863 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
864 stderr=subprocess.PIPE)
865 stdoutdata, stderrdata = proc.communicate()
866 if proc.returncode != 0:
867 raise ExternalError(
868 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
869 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800870
Tao Baof47bf0f2018-03-21 23:28:51 -0700871 for line in stdoutdata.split("\n"):
872 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800873 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
874 if m:
875 return m.group(1)
876 raise ExternalError("No minSdkVersion returned by aapt")
877
878
879def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700880 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800881
Tao Baof47bf0f2018-03-21 23:28:51 -0700882 If minSdkVersion is set to a codename, it is translated to a number using the
883 provided map.
884
885 Args:
886 apk_name: The APK filename.
887
888 Returns:
889 The parsed SDK version number.
890
891 Raises:
892 ExternalError: On failing to get the min SDK version number.
893 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800894 version = GetMinSdkVersion(apk_name)
895 try:
896 return int(version)
897 except ValueError:
898 # Not a decimal number. Codename?
899 if version in codename_to_api_level_map:
900 return codename_to_api_level_map[version]
901 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700902 raise ExternalError(
903 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
904 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800905
906
907def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800908 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700909 """Sign the input_name zip/jar/apk, producing output_name. Use the
910 given key and password (the latter may be None if the key does not
911 have a password.
912
Doug Zongker951495f2009-08-14 12:44:19 -0700913 If whole_file is true, use the "-w" option to SignApk to embed a
914 signature that covers the whole file in the archive comment of the
915 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800916
917 min_api_level is the API Level (int) of the oldest platform this file may end
918 up on. If not specified for an APK, the API Level is obtained by interpreting
919 the minSdkVersion attribute of the APK's AndroidManifest.xml.
920
921 codename_to_api_level_map is needed to translate the codename which may be
922 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700923 """
Tao Bao76def242017-11-21 09:25:31 -0800924 if codename_to_api_level_map is None:
925 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700926
Alex Klyubin9667b182015-12-10 13:38:50 -0800927 java_library_path = os.path.join(
928 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
929
Tao Baoe95540e2016-11-08 12:08:53 -0800930 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
931 ["-Djava.library.path=" + java_library_path,
932 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
933 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700934 if whole_file:
935 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800936
937 min_sdk_version = min_api_level
938 if min_sdk_version is None:
939 if not whole_file:
940 min_sdk_version = GetMinSdkVersionInt(
941 input_name, codename_to_api_level_map)
942 if min_sdk_version is not None:
943 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
944
T.R. Fullhart37e10522013-03-18 10:31:26 -0700945 cmd.extend([key + OPTIONS.public_key_suffix,
946 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800947 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700948
Tao Bao73dd4f42018-10-04 16:25:33 -0700949 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700950 if password is not None:
951 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -0700952 stdoutdata, _ = proc.communicate(password)
953 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700954 raise ExternalError(
955 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -0700956 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700957
Doug Zongkereef39442009-04-02 12:14:19 -0700958
Doug Zongker37974732010-09-16 17:44:38 -0700959def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800960 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700961
Tao Bao9dd909e2017-11-14 11:27:32 -0800962 For non-AVB images, raise exception if the data is too big. Print a warning
963 if the data is nearing the maximum size.
964
965 For AVB images, the actual image size should be identical to the limit.
966
967 Args:
968 data: A string that contains all the data for the partition.
969 target: The partition name. The ".img" suffix is optional.
970 info_dict: The dict to be looked up for relevant info.
971 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700972 if target.endswith(".img"):
973 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700974 mount_point = "/" + target
975
Ying Wangf8824af2014-06-03 14:07:27 -0700976 fs_type = None
977 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700978 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700979 if mount_point == "/userdata":
980 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700981 p = info_dict["fstab"][mount_point]
982 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800983 device = p.device
984 if "/" in device:
985 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -0800986 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -0700987 if not fs_type or not limit:
988 return
Doug Zongkereef39442009-04-02 12:14:19 -0700989
Andrew Boie0f9aec82012-02-14 09:32:52 -0800990 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800991 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
992 # path.
993 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
994 if size != limit:
995 raise ExternalError(
996 "Mismatching image size for %s: expected %d actual %d" % (
997 target, limit, size))
998 else:
999 pct = float(size) * 100.0 / limit
1000 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1001 if pct >= 99.0:
1002 raise ExternalError(msg)
1003 elif pct >= 95.0:
1004 print("\n WARNING: %s\n" % (msg,))
1005 elif OPTIONS.verbose:
1006 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001007
1008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001009def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001010 """Parses the APK certs info from a given target-files zip.
1011
1012 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1013 tuple with the following elements: (1) a dictionary that maps packages to
1014 certs (based on the "certificate" and "private_key" attributes in the file;
1015 (2) a string representing the extension of compressed APKs in the target files
1016 (e.g ".gz", ".bro").
1017
1018 Args:
1019 tf_zip: The input target_files ZipFile (already open).
1020
1021 Returns:
1022 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1023 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1024 no compressed APKs.
1025 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001026 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001027 compressed_extension = None
1028
Tao Bao0f990332017-09-08 19:02:54 -07001029 # META/apkcerts.txt contains the info for _all_ the packages known at build
1030 # time. Filter out the ones that are not installed.
1031 installed_files = set()
1032 for name in tf_zip.namelist():
1033 basename = os.path.basename(name)
1034 if basename:
1035 installed_files.add(basename)
1036
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001037 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1038 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001039 if not line:
1040 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001041 m = re.match(
1042 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1043 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1044 line)
1045 if not m:
1046 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001047
Tao Bao818ddf52018-01-05 11:17:34 -08001048 matches = m.groupdict()
1049 cert = matches["CERT"]
1050 privkey = matches["PRIVKEY"]
1051 name = matches["NAME"]
1052 this_compressed_extension = matches["COMPRESSED"]
1053
1054 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1055 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1056 if cert in SPECIAL_CERT_STRINGS and not privkey:
1057 certmap[name] = cert
1058 elif (cert.endswith(OPTIONS.public_key_suffix) and
1059 privkey.endswith(OPTIONS.private_key_suffix) and
1060 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1061 certmap[name] = cert[:-public_key_suffix_len]
1062 else:
1063 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1064
1065 if not this_compressed_extension:
1066 continue
1067
1068 # Only count the installed files.
1069 filename = name + '.' + this_compressed_extension
1070 if filename not in installed_files:
1071 continue
1072
1073 # Make sure that all the values in the compression map have the same
1074 # extension. We don't support multiple compression methods in the same
1075 # system image.
1076 if compressed_extension:
1077 if this_compressed_extension != compressed_extension:
1078 raise ValueError(
1079 "Multiple compressed extensions: {} vs {}".format(
1080 compressed_extension, this_compressed_extension))
1081 else:
1082 compressed_extension = this_compressed_extension
1083
1084 return (certmap,
1085 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001086
1087
Doug Zongkereef39442009-04-02 12:14:19 -07001088COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001089Global options
1090
1091 -p (--path) <dir>
1092 Prepend <dir>/bin to the list of places to search for binaries run by this
1093 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001094
Doug Zongker05d3dea2009-06-22 11:32:31 -07001095 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001096 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001097
Tao Bao30df8b42018-04-23 15:32:53 -07001098 -x (--extra) <key=value>
1099 Add a key/value pair to the 'extras' dict, which device-specific extension
1100 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001101
Doug Zongkereef39442009-04-02 12:14:19 -07001102 -v (--verbose)
1103 Show command lines being executed.
1104
1105 -h (--help)
1106 Display this usage message and exit.
1107"""
1108
1109def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001110 print(docstring.rstrip("\n"))
1111 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001112
1113
1114def ParseOptions(argv,
1115 docstring,
1116 extra_opts="", extra_long_opts=(),
1117 extra_option_handler=None):
1118 """Parse the options in argv and return any arguments that aren't
1119 flags. docstring is the calling module's docstring, to be displayed
1120 for errors and -h. extra_opts and extra_long_opts are for flags
1121 defined by the caller, which are processed by passing them to
1122 extra_option_handler."""
1123
1124 try:
1125 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001126 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001127 ["help", "verbose", "path=", "signapk_path=",
1128 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001129 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001130 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1131 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001132 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001133 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001134 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001135 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001136 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001137 sys.exit(2)
1138
Doug Zongkereef39442009-04-02 12:14:19 -07001139 for o, a in opts:
1140 if o in ("-h", "--help"):
1141 Usage(docstring)
1142 sys.exit()
1143 elif o in ("-v", "--verbose"):
1144 OPTIONS.verbose = True
1145 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001146 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001147 elif o in ("--signapk_path",):
1148 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001149 elif o in ("--signapk_shared_library_path",):
1150 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001151 elif o in ("--extra_signapk_args",):
1152 OPTIONS.extra_signapk_args = shlex.split(a)
1153 elif o in ("--java_path",):
1154 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001155 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001156 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001157 elif o in ("--public_key_suffix",):
1158 OPTIONS.public_key_suffix = a
1159 elif o in ("--private_key_suffix",):
1160 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001161 elif o in ("--boot_signer_path",):
1162 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001163 elif o in ("--boot_signer_args",):
1164 OPTIONS.boot_signer_args = shlex.split(a)
1165 elif o in ("--verity_signer_path",):
1166 OPTIONS.verity_signer_path = a
1167 elif o in ("--verity_signer_args",):
1168 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001169 elif o in ("-s", "--device_specific"):
1170 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001171 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001172 key, value = a.split("=", 1)
1173 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001174 else:
1175 if extra_option_handler is None or not extra_option_handler(o, a):
1176 assert False, "unknown option \"%s\"" % (o,)
1177
Doug Zongker85448772014-09-09 14:59:20 -07001178 if OPTIONS.search_path:
1179 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1180 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001181
1182 return args
1183
1184
Tao Bao4c851b12016-09-19 13:54:38 -07001185def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001186 """Make a temp file and add it to the list of things to be deleted
1187 when Cleanup() is called. Return the filename."""
1188 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1189 os.close(fd)
1190 OPTIONS.tempfiles.append(fn)
1191 return fn
1192
1193
Tao Bao1c830bf2017-12-25 10:43:47 -08001194def MakeTempDir(prefix='tmp', suffix=''):
1195 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1196
1197 Returns:
1198 The absolute pathname of the new directory.
1199 """
1200 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1201 OPTIONS.tempfiles.append(dir_name)
1202 return dir_name
1203
1204
Doug Zongkereef39442009-04-02 12:14:19 -07001205def Cleanup():
1206 for i in OPTIONS.tempfiles:
1207 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001208 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001209 else:
1210 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001211 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001212
1213
1214class PasswordManager(object):
1215 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001216 self.editor = os.getenv("EDITOR")
1217 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001218
1219 def GetPasswords(self, items):
1220 """Get passwords corresponding to each string in 'items',
1221 returning a dict. (The dict may have keys in addition to the
1222 values in 'items'.)
1223
1224 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1225 user edit that file to add more needed passwords. If no editor is
1226 available, or $ANDROID_PW_FILE isn't define, prompts the user
1227 interactively in the ordinary way.
1228 """
1229
1230 current = self.ReadFile()
1231
1232 first = True
1233 while True:
1234 missing = []
1235 for i in items:
1236 if i not in current or not current[i]:
1237 missing.append(i)
1238 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001239 if not missing:
1240 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001241
1242 for i in missing:
1243 current[i] = ""
1244
1245 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001246 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001247 answer = raw_input("try to edit again? [y]> ").strip()
1248 if answer and answer[0] not in 'yY':
1249 raise RuntimeError("key passwords unavailable")
1250 first = False
1251
1252 current = self.UpdateAndReadFile(current)
1253
Dan Albert8b72aef2015-03-23 19:13:21 -07001254 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001255 """Prompt the user to enter a value (password) for each key in
1256 'current' whose value is fales. Returns a new dict with all the
1257 values.
1258 """
1259 result = {}
1260 for k, v in sorted(current.iteritems()):
1261 if v:
1262 result[k] = v
1263 else:
1264 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001265 result[k] = getpass.getpass(
1266 "Enter password for %s key> " % k).strip()
1267 if result[k]:
1268 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001269 return result
1270
1271 def UpdateAndReadFile(self, current):
1272 if not self.editor or not self.pwfile:
1273 return self.PromptResult(current)
1274
1275 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001276 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001277 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1278 f.write("# (Additional spaces are harmless.)\n\n")
1279
1280 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001281 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1282 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001283 f.write("[[[ %s ]]] %s\n" % (v, k))
1284 if not v and first_line is None:
1285 # position cursor on first line with no password.
1286 first_line = i + 4
1287 f.close()
1288
Tao Bao986ee862018-10-04 15:46:16 -07001289 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001290
1291 return self.ReadFile()
1292
1293 def ReadFile(self):
1294 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001295 if self.pwfile is None:
1296 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001297 try:
1298 f = open(self.pwfile, "r")
1299 for line in f:
1300 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001301 if not line or line[0] == '#':
1302 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001303 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1304 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001305 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001306 else:
1307 result[m.group(2)] = m.group(1)
1308 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001309 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001310 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001311 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001312 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001313
1314
Dan Albert8e0178d2015-01-27 15:53:15 -08001315def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1316 compress_type=None):
1317 import datetime
1318
1319 # http://b/18015246
1320 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1321 # for files larger than 2GiB. We can work around this by adjusting their
1322 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1323 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1324 # it isn't clear to me exactly what circumstances cause this).
1325 # `zipfile.write()` must be used directly to work around this.
1326 #
1327 # This mess can be avoided if we port to python3.
1328 saved_zip64_limit = zipfile.ZIP64_LIMIT
1329 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1330
1331 if compress_type is None:
1332 compress_type = zip_file.compression
1333 if arcname is None:
1334 arcname = filename
1335
1336 saved_stat = os.stat(filename)
1337
1338 try:
1339 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1340 # file to be zipped and reset it when we're done.
1341 os.chmod(filename, perms)
1342
1343 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001344 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1345 # intentional. zip stores datetimes in local time without a time zone
1346 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1347 # in the zip archive.
1348 local_epoch = datetime.datetime.fromtimestamp(0)
1349 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001350 os.utime(filename, (timestamp, timestamp))
1351
1352 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1353 finally:
1354 os.chmod(filename, saved_stat.st_mode)
1355 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1356 zipfile.ZIP64_LIMIT = saved_zip64_limit
1357
1358
Tao Bao58c1b962015-05-20 09:32:18 -07001359def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001360 compress_type=None):
1361 """Wrap zipfile.writestr() function to work around the zip64 limit.
1362
1363 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1364 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1365 when calling crc32(bytes).
1366
1367 But it still works fine to write a shorter string into a large zip file.
1368 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1369 when we know the string won't be too long.
1370 """
1371
1372 saved_zip64_limit = zipfile.ZIP64_LIMIT
1373 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1374
1375 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1376 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001377 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001378 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001379 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001380 else:
Tao Baof3282b42015-04-01 11:21:55 -07001381 zinfo = zinfo_or_arcname
1382
1383 # If compress_type is given, it overrides the value in zinfo.
1384 if compress_type is not None:
1385 zinfo.compress_type = compress_type
1386
Tao Bao58c1b962015-05-20 09:32:18 -07001387 # If perms is given, it has a priority.
1388 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001389 # If perms doesn't set the file type, mark it as a regular file.
1390 if perms & 0o770000 == 0:
1391 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001392 zinfo.external_attr = perms << 16
1393
Tao Baof3282b42015-04-01 11:21:55 -07001394 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001395 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1396
Dan Albert8b72aef2015-03-23 19:13:21 -07001397 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001398 zipfile.ZIP64_LIMIT = saved_zip64_limit
1399
1400
Tao Bao89d7ab22017-12-14 17:05:33 -08001401def ZipDelete(zip_filename, entries):
1402 """Deletes entries from a ZIP file.
1403
1404 Since deleting entries from a ZIP file is not supported, it shells out to
1405 'zip -d'.
1406
1407 Args:
1408 zip_filename: The name of the ZIP file.
1409 entries: The name of the entry, or the list of names to be deleted.
1410
1411 Raises:
1412 AssertionError: In case of non-zero return from 'zip'.
1413 """
1414 if isinstance(entries, basestring):
1415 entries = [entries]
1416 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001417 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001418
1419
Tao Baof3282b42015-04-01 11:21:55 -07001420def ZipClose(zip_file):
1421 # http://b/18015246
1422 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1423 # central directory.
1424 saved_zip64_limit = zipfile.ZIP64_LIMIT
1425 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1426
1427 zip_file.close()
1428
1429 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001430
1431
1432class DeviceSpecificParams(object):
1433 module = None
1434 def __init__(self, **kwargs):
1435 """Keyword arguments to the constructor become attributes of this
1436 object, which is passed to all functions in the device-specific
1437 module."""
1438 for k, v in kwargs.iteritems():
1439 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001440 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001441
1442 if self.module is None:
1443 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001444 if not path:
1445 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001446 try:
1447 if os.path.isdir(path):
1448 info = imp.find_module("releasetools", [path])
1449 else:
1450 d, f = os.path.split(path)
1451 b, x = os.path.splitext(f)
1452 if x == ".py":
1453 f = b
1454 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001455 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001456 self.module = imp.load_module("device_specific", *info)
1457 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001458 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001459
1460 def _DoCall(self, function_name, *args, **kwargs):
1461 """Call the named function in the device-specific module, passing
1462 the given args and kwargs. The first argument to the call will be
1463 the DeviceSpecific object itself. If there is no module, or the
1464 module does not define the function, return the value of the
1465 'default' kwarg (which itself defaults to None)."""
1466 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001467 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001468 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1469
1470 def FullOTA_Assertions(self):
1471 """Called after emitting the block of assertions at the top of a
1472 full OTA package. Implementations can add whatever additional
1473 assertions they like."""
1474 return self._DoCall("FullOTA_Assertions")
1475
Doug Zongkere5ff5902012-01-17 10:55:37 -08001476 def FullOTA_InstallBegin(self):
1477 """Called at the start of full OTA installation."""
1478 return self._DoCall("FullOTA_InstallBegin")
1479
Doug Zongker05d3dea2009-06-22 11:32:31 -07001480 def FullOTA_InstallEnd(self):
1481 """Called at the end of full OTA installation; typically this is
1482 used to install the image for the device's baseband processor."""
1483 return self._DoCall("FullOTA_InstallEnd")
1484
1485 def IncrementalOTA_Assertions(self):
1486 """Called after emitting the block of assertions at the top of an
1487 incremental OTA package. Implementations can add whatever
1488 additional assertions they like."""
1489 return self._DoCall("IncrementalOTA_Assertions")
1490
Doug Zongkere5ff5902012-01-17 10:55:37 -08001491 def IncrementalOTA_VerifyBegin(self):
1492 """Called at the start of the verification phase of incremental
1493 OTA installation; additional checks can be placed here to abort
1494 the script before any changes are made."""
1495 return self._DoCall("IncrementalOTA_VerifyBegin")
1496
Doug Zongker05d3dea2009-06-22 11:32:31 -07001497 def IncrementalOTA_VerifyEnd(self):
1498 """Called at the end of the verification phase of incremental OTA
1499 installation; additional checks can be placed here to abort the
1500 script before any changes are made."""
1501 return self._DoCall("IncrementalOTA_VerifyEnd")
1502
Doug Zongkere5ff5902012-01-17 10:55:37 -08001503 def IncrementalOTA_InstallBegin(self):
1504 """Called at the start of incremental OTA installation (after
1505 verification is complete)."""
1506 return self._DoCall("IncrementalOTA_InstallBegin")
1507
Doug Zongker05d3dea2009-06-22 11:32:31 -07001508 def IncrementalOTA_InstallEnd(self):
1509 """Called at the end of incremental OTA installation; typically
1510 this is used to install the image for the device's baseband
1511 processor."""
1512 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001513
Tao Bao9bc6bb22015-11-09 16:58:28 -08001514 def VerifyOTA_Assertions(self):
1515 return self._DoCall("VerifyOTA_Assertions")
1516
Tao Bao76def242017-11-21 09:25:31 -08001517
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001518class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001519 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001520 self.name = name
1521 self.data = data
1522 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001523 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001524 self.sha1 = sha1(data).hexdigest()
1525
1526 @classmethod
1527 def FromLocalFile(cls, name, diskname):
1528 f = open(diskname, "rb")
1529 data = f.read()
1530 f.close()
1531 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001532
1533 def WriteToTemp(self):
1534 t = tempfile.NamedTemporaryFile()
1535 t.write(self.data)
1536 t.flush()
1537 return t
1538
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001539 def WriteToDir(self, d):
1540 with open(os.path.join(d, self.name), "wb") as fp:
1541 fp.write(self.data)
1542
Geremy Condra36bd3652014-02-06 19:45:10 -08001543 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001544 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001545
Tao Bao76def242017-11-21 09:25:31 -08001546
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001547DIFF_PROGRAM_BY_EXT = {
1548 ".gz" : "imgdiff",
1549 ".zip" : ["imgdiff", "-z"],
1550 ".jar" : ["imgdiff", "-z"],
1551 ".apk" : ["imgdiff", "-z"],
1552 ".img" : "imgdiff",
1553 }
1554
Tao Bao76def242017-11-21 09:25:31 -08001555
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001556class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001557 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001558 self.tf = tf
1559 self.sf = sf
1560 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001561 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001562
1563 def ComputePatch(self):
1564 """Compute the patch (as a string of data) needed to turn sf into
1565 tf. Returns the same tuple as GetPatch()."""
1566
1567 tf = self.tf
1568 sf = self.sf
1569
Doug Zongker24cd2802012-08-14 16:36:15 -07001570 if self.diff_program:
1571 diff_program = self.diff_program
1572 else:
1573 ext = os.path.splitext(tf.name)[1]
1574 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001575
1576 ttemp = tf.WriteToTemp()
1577 stemp = sf.WriteToTemp()
1578
1579 ext = os.path.splitext(tf.name)[1]
1580
1581 try:
1582 ptemp = tempfile.NamedTemporaryFile()
1583 if isinstance(diff_program, list):
1584 cmd = copy.copy(diff_program)
1585 else:
1586 cmd = [diff_program]
1587 cmd.append(stemp.name)
1588 cmd.append(ttemp.name)
1589 cmd.append(ptemp.name)
1590 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001591 err = []
1592 def run():
1593 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001594 if e:
1595 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001596 th = threading.Thread(target=run)
1597 th.start()
1598 th.join(timeout=300) # 5 mins
1599 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001600 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001601 p.terminate()
1602 th.join(5)
1603 if th.is_alive():
1604 p.kill()
1605 th.join()
1606
Tianjie Xua2a9f992018-01-05 15:15:54 -08001607 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001608 print("WARNING: failure running %s:\n%s\n" % (
1609 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001610 self.patch = None
1611 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001612 diff = ptemp.read()
1613 finally:
1614 ptemp.close()
1615 stemp.close()
1616 ttemp.close()
1617
1618 self.patch = diff
1619 return self.tf, self.sf, self.patch
1620
1621
1622 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001623 """Returns a tuple of (target_file, source_file, patch_data).
1624
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001625 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001626 computing the patch failed.
1627 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001628 return self.tf, self.sf, self.patch
1629
1630
1631def ComputeDifferences(diffs):
1632 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001633 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001634
1635 # Do the largest files first, to try and reduce the long-pole effect.
1636 by_size = [(i.tf.size, i) for i in diffs]
1637 by_size.sort(reverse=True)
1638 by_size = [i[1] for i in by_size]
1639
1640 lock = threading.Lock()
1641 diff_iter = iter(by_size) # accessed under lock
1642
1643 def worker():
1644 try:
1645 lock.acquire()
1646 for d in diff_iter:
1647 lock.release()
1648 start = time.time()
1649 d.ComputePatch()
1650 dur = time.time() - start
1651 lock.acquire()
1652
1653 tf, sf, patch = d.GetPatch()
1654 if sf.name == tf.name:
1655 name = tf.name
1656 else:
1657 name = "%s (%s)" % (tf.name, sf.name)
1658 if patch is None:
Tao Bao76def242017-11-21 09:25:31 -08001659 print(
1660 "patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001661 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001662 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1663 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001664 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001665 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001666 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001667 raise
1668
1669 # start worker threads; wait for them all to finish.
1670 threads = [threading.Thread(target=worker)
1671 for i in range(OPTIONS.worker_threads)]
1672 for th in threads:
1673 th.start()
1674 while threads:
1675 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001676
1677
Dan Albert8b72aef2015-03-23 19:13:21 -07001678class BlockDifference(object):
1679 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001680 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001681 self.tgt = tgt
1682 self.src = src
1683 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001684 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001685 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001686
Tao Baodd2a5892015-03-12 12:32:37 -07001687 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001688 version = max(
1689 int(i) for i in
1690 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001691 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001692 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001693
1694 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001695 version=self.version,
1696 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001697 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001698 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001699 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001700 self.touched_src_ranges = b.touched_src_ranges
1701 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001702
Tao Baoaac4ad52015-10-16 15:26:34 -07001703 if src is None:
1704 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1705 else:
1706 _, self.device = GetTypeAndDevice("/" + partition,
1707 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001708
Tao Baod8d14be2016-02-04 14:26:02 -08001709 @property
1710 def required_cache(self):
1711 return self._required_cache
1712
Tao Bao76def242017-11-21 09:25:31 -08001713 def WriteScript(self, script, output_zip, progress=None,
1714 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001715 if not self.src:
1716 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001717 script.Print("Patching %s image unconditionally..." % (self.partition,))
1718 else:
1719 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001720
Dan Albert8b72aef2015-03-23 19:13:21 -07001721 if progress:
1722 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001723 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001724
1725 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001726 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001727
Tao Bao9bc6bb22015-11-09 16:58:28 -08001728 def WriteStrictVerifyScript(self, script):
1729 """Verify all the blocks in the care_map, including clobbered blocks.
1730
1731 This differs from the WriteVerifyScript() function: a) it prints different
1732 error messages; b) it doesn't allow half-way updated images to pass the
1733 verification."""
1734
1735 partition = self.partition
1736 script.Print("Verifying %s..." % (partition,))
1737 ranges = self.tgt.care_map
1738 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001739 script.AppendExtra(
1740 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1741 'ui_print("\\"%s\\" has unexpected contents.");' % (
1742 self.device, ranges_str,
1743 self.tgt.TotalSha1(include_clobbered_blocks=True),
1744 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001745 script.AppendExtra("")
1746
Tao Baod522bdc2016-04-12 15:53:16 -07001747 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001748 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001749
1750 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001751 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001752 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001753
1754 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001755 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001756 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001757 ranges = self.touched_src_ranges
1758 expected_sha1 = self.touched_src_sha1
1759 else:
1760 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1761 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001762
1763 # No blocks to be checked, skipping.
1764 if not ranges:
1765 return
1766
Tao Bao5ece99d2015-05-12 11:42:31 -07001767 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001768 script.AppendExtra(
1769 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1770 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1771 '"%s.patch.dat")) then' % (
1772 self.device, ranges_str, expected_sha1,
1773 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001774 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001775 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001776
Tianjie Xufc3422a2015-12-15 11:53:59 -08001777 if self.version >= 4:
1778
1779 # Bug: 21124327
1780 # When generating incrementals for the system and vendor partitions in
1781 # version 4 or newer, explicitly check the first block (which contains
1782 # the superblock) of the partition to see if it's what we expect. If
1783 # this check fails, give an explicit log message about the partition
1784 # having been remounted R/W (the most likely explanation).
1785 if self.check_first_block:
1786 script.AppendExtra('check_first_block("%s");' % (self.device,))
1787
1788 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001789 if partition == "system":
1790 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1791 else:
1792 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001793 script.AppendExtra((
1794 'ifelse (block_image_recover("{device}", "{ranges}") && '
1795 'block_image_verify("{device}", '
1796 'package_extract_file("{partition}.transfer.list"), '
1797 '"{partition}.new.dat", "{partition}.patch.dat"), '
1798 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001799 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001800 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001801 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001802
Tao Baodd2a5892015-03-12 12:32:37 -07001803 # Abort the OTA update. Note that the incremental OTA cannot be applied
1804 # even if it may match the checksum of the target partition.
1805 # a) If version < 3, operations like move and erase will make changes
1806 # unconditionally and damage the partition.
1807 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001808 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001809 if partition == "system":
1810 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1811 else:
1812 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1813 script.AppendExtra((
1814 'abort("E%d: %s partition has unexpected contents");\n'
1815 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001816
Tao Bao5fcaaef2015-06-01 13:40:49 -07001817 def _WritePostInstallVerifyScript(self, script):
1818 partition = self.partition
1819 script.Print('Verifying the updated %s image...' % (partition,))
1820 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1821 ranges = self.tgt.care_map
1822 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001823 script.AppendExtra(
1824 'if range_sha1("%s", "%s") == "%s" then' % (
1825 self.device, ranges_str,
1826 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001827
1828 # Bug: 20881595
1829 # Verify that extended blocks are really zeroed out.
1830 if self.tgt.extended:
1831 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001832 script.AppendExtra(
1833 'if range_sha1("%s", "%s") == "%s" then' % (
1834 self.device, ranges_str,
1835 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001836 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001837 if partition == "system":
1838 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1839 else:
1840 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001841 script.AppendExtra(
1842 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001843 ' abort("E%d: %s partition has unexpected non-zero contents after '
1844 'OTA update");\n'
1845 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001846 else:
1847 script.Print('Verified the updated %s image.' % (partition,))
1848
Tianjie Xu209db462016-05-24 17:34:52 -07001849 if partition == "system":
1850 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1851 else:
1852 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1853
Tao Bao5fcaaef2015-06-01 13:40:49 -07001854 script.AppendExtra(
1855 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001856 ' abort("E%d: %s partition has unexpected contents after OTA '
1857 'update");\n'
1858 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001859
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001860 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001861 ZipWrite(output_zip,
1862 '{}.transfer.list'.format(self.path),
1863 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001864
Tao Bao76def242017-11-21 09:25:31 -08001865 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1866 # its size. Quailty 9 almost triples the compression time but doesn't
1867 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001868 # zip | brotli(quality 6) | brotli(quality 9)
1869 # compressed_size: 942M | 869M (~8% reduced) | 854M
1870 # compression_time: 75s | 265s | 719s
1871 # decompression_time: 15s | 25s | 25s
1872
1873 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001874 brotli_cmd = ['brotli', '--quality=6',
1875 '--output={}.new.dat.br'.format(self.path),
1876 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001877 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07001878 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001879
1880 new_data_name = '{}.new.dat.br'.format(self.partition)
1881 ZipWrite(output_zip,
1882 '{}.new.dat.br'.format(self.path),
1883 new_data_name,
1884 compress_type=zipfile.ZIP_STORED)
1885 else:
1886 new_data_name = '{}.new.dat'.format(self.partition)
1887 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1888
Dan Albert8e0178d2015-01-27 15:53:15 -08001889 ZipWrite(output_zip,
1890 '{}.patch.dat'.format(self.path),
1891 '{}.patch.dat'.format(self.partition),
1892 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001893
Tianjie Xu209db462016-05-24 17:34:52 -07001894 if self.partition == "system":
1895 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1896 else:
1897 code = ErrorCode.VENDOR_UPDATE_FAILURE
1898
Dan Albert8e0178d2015-01-27 15:53:15 -08001899 call = ('block_image_update("{device}", '
1900 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001901 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001902 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001903 device=self.device, partition=self.partition,
1904 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001905 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001906
Dan Albert8b72aef2015-03-23 19:13:21 -07001907 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001908 data = source.ReadRangeSet(ranges)
1909 ctx = sha1()
1910
1911 for p in data:
1912 ctx.update(p)
1913
1914 return ctx.hexdigest()
1915
Tao Baoe9b61912015-07-09 17:37:49 -07001916 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1917 """Return the hash value for all zero blocks."""
1918 zero_block = '\x00' * 4096
1919 ctx = sha1()
1920 for _ in range(num_blocks):
1921 ctx.update(zero_block)
1922
1923 return ctx.hexdigest()
1924
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001925
1926DataImage = blockimgdiff.DataImage
1927
Tao Bao76def242017-11-21 09:25:31 -08001928
Doug Zongker96a57e72010-09-26 14:57:41 -07001929# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001930PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001931 "ext4": "EMMC",
1932 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001933 "f2fs": "EMMC",
1934 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001935}
Doug Zongker96a57e72010-09-26 14:57:41 -07001936
Tao Bao76def242017-11-21 09:25:31 -08001937
Doug Zongker96a57e72010-09-26 14:57:41 -07001938def GetTypeAndDevice(mount_point, info):
1939 fstab = info["fstab"]
1940 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001941 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1942 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001943 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001944 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001945
1946
1947def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001948 """Parses and converts a PEM-encoded certificate into DER-encoded.
1949
1950 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1951
1952 Returns:
1953 The decoded certificate string.
1954 """
1955 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001956 save = False
1957 for line in data.split("\n"):
1958 if "--END CERTIFICATE--" in line:
1959 break
1960 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001961 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001962 if "--BEGIN CERTIFICATE--" in line:
1963 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001964 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001965 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001966
Tao Bao04e1f012018-02-04 12:13:35 -08001967
1968def ExtractPublicKey(cert):
1969 """Extracts the public key (PEM-encoded) from the given certificate file.
1970
1971 Args:
1972 cert: The certificate filename.
1973
1974 Returns:
1975 The public key string.
1976
1977 Raises:
1978 AssertionError: On non-zero return from 'openssl'.
1979 """
1980 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1981 # While openssl 1.1 writes the key into the given filename followed by '-out',
1982 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1983 # stdout instead.
1984 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1985 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1986 pubkey, stderrdata = proc.communicate()
1987 assert proc.returncode == 0, \
1988 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1989 return pubkey
1990
1991
Doug Zongker412c02f2014-02-13 10:58:24 -08001992def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1993 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001994 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001995
Tao Bao6d5d6232018-03-09 17:04:42 -08001996 Most of the space in the boot and recovery images is just the kernel, which is
1997 identical for the two, so the resulting patch should be efficient. Add it to
1998 the output zip, along with a shell script that is run from init.rc on first
1999 boot to actually do the patching and install the new recovery image.
2000
2001 Args:
2002 input_dir: The top-level input directory of the target-files.zip.
2003 output_sink: The callback function that writes the result.
2004 recovery_img: File object for the recovery image.
2005 boot_img: File objects for the boot image.
2006 info_dict: A dict returned by common.LoadInfoDict() on the input
2007 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002008 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002009 if info_dict is None:
2010 info_dict = OPTIONS.info_dict
2011
Tao Bao6d5d6232018-03-09 17:04:42 -08002012 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002013
Tao Baof2cffbd2015-07-22 12:33:18 -07002014 if full_recovery_image:
2015 output_sink("etc/recovery.img", recovery_img.data)
2016
2017 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002018 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002019 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002020 # With system-root-image, boot and recovery images will have mismatching
2021 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2022 # to handle such a case.
2023 if system_root_image:
2024 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002025 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002026 assert not os.path.exists(path)
2027 else:
2028 diff_program = ["imgdiff"]
2029 if os.path.exists(path):
2030 diff_program.append("-b")
2031 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002032 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002033 else:
2034 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002035
2036 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2037 _, _, patch = d.ComputePatch()
2038 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002039
Dan Albertebb19aa2015-03-27 19:11:53 -07002040 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002041 # The following GetTypeAndDevice()s need to use the path in the target
2042 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002043 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2044 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2045 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002046 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002047
Tao Baof2cffbd2015-07-22 12:33:18 -07002048 if full_recovery_image:
2049 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002050if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2051 applypatch \\
2052 --flash /system/etc/recovery.img \\
2053 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2054 log -t recovery "Installing new recovery image: succeeded" || \\
2055 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002056else
2057 log -t recovery "Recovery image already installed"
2058fi
2059""" % {'type': recovery_type,
2060 'device': recovery_device,
2061 'sha1': recovery_img.sha1,
2062 'size': recovery_img.size}
2063 else:
2064 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002065if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2066 applypatch %(bonus_args)s \\
2067 --patch /system/recovery-from-boot.p \\
2068 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2069 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2070 log -t recovery "Installing new recovery image: succeeded" || \\
2071 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002072else
2073 log -t recovery "Recovery image already installed"
2074fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002075""" % {'boot_size': boot_img.size,
2076 'boot_sha1': boot_img.sha1,
2077 'recovery_size': recovery_img.size,
2078 'recovery_sha1': recovery_img.sha1,
2079 'boot_type': boot_type,
2080 'boot_device': boot_device,
2081 'recovery_type': recovery_type,
2082 'recovery_device': recovery_device,
2083 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002084
2085 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002086 # in the L release.
2087 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002088
Tao Bao89fbb0f2017-01-10 10:47:58 -08002089 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002090
2091 output_sink(sh_location, sh)