blob: 4ad30ec6787670b710336597f6afddcbab78b461 [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
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
33
Tao Baof3282b42015-04-01 11:21:55 -070034from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080035
Doug Zongkereef39442009-04-02 12:14:19 -070036
Dan Albert8b72aef2015-03-23 19:13:21 -070037class Options(object):
38 def __init__(self):
39 platform_search_path = {
40 "linux2": "out/host/linux-x86",
41 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070042 }
Doug Zongker85448772014-09-09 14:59:20 -070043
Dan Albert8b72aef2015-03-23 19:13:21 -070044 self.search_path = platform_search_path.get(sys.platform, None)
45 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080046 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070047 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080049 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070050 self.public_key_suffix = ".x509.pem"
51 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070054 self.boot_signer_args = []
55 self.verity_signer_path = None
56 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070057 self.verbose = False
58 self.tempfiles = []
59 self.device_specific = None
60 self.extras = {}
61 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070062 self.source_info_dict = None
63 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070064 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070065 # Stash size cannot exceed cache_size * threshold.
66 self.cache_size = None
67 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070068
69
70OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070071
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080072
73# Values for "certificate" in apkcerts that mean special things.
74SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
75
Tianjie Xu209db462016-05-24 17:34:52 -070076class ErrorCode(object):
77 """Define error_codes for failures that happen during the actual
78 update package installation.
79
80 Error codes 0-999 are reserved for failures before the package
81 installation (i.e. low battery, package verification failure).
82 Detailed code in 'bootable/recovery/error_code.h' """
83
84 SYSTEM_VERIFICATION_FAILURE = 1000
85 SYSTEM_UPDATE_FAILURE = 1001
86 SYSTEM_UNEXPECTED_CONTENTS = 1002
87 SYSTEM_NONZERO_CONTENTS = 1003
88 SYSTEM_RECOVER_FAILURE = 1004
89 VENDOR_VERIFICATION_FAILURE = 2000
90 VENDOR_UPDATE_FAILURE = 2001
91 VENDOR_UNEXPECTED_CONTENTS = 2002
92 VENDOR_NONZERO_CONTENTS = 2003
93 VENDOR_RECOVER_FAILURE = 2004
94 OEM_PROP_MISMATCH = 3000
95 FINGERPRINT_MISMATCH = 3001
96 THUMBPRINT_MISMATCH = 3002
97 OLDER_BUILD = 3003
98 DEVICE_MISMATCH = 3004
99 BAD_PATCH_FILE = 3005
100 INSUFFICIENT_CACHE_SPACE = 3006
101 TUNE_PARTITION_FAILURE = 3007
102 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800103
Dan Albert8b72aef2015-03-23 19:13:21 -0700104class ExternalError(RuntimeError):
105 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700106
107
108def Run(args, **kwargs):
109 """Create and return a subprocess.Popen object, printing the command
110 line on the terminal if -v was specified."""
111 if OPTIONS.verbose:
112 print " running: ", " ".join(args)
113 return subprocess.Popen(args, **kwargs)
114
115
Ying Wang7e6d4e42010-12-13 16:25:36 -0800116def CloseInheritedPipes():
117 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
118 before doing other work."""
119 if platform.system() != "Darwin":
120 return
121 for d in range(3, 1025):
122 try:
123 stat = os.fstat(d)
124 if stat is not None:
125 pipebit = stat[0] & 0x1000
126 if pipebit != 0:
127 os.close(d)
128 except OSError:
129 pass
130
131
Tao Bao2c15d9e2015-07-09 11:51:16 -0700132def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700133 """Read and parse the META/misc_info.txt key/value pairs from the
134 input target files and return a dict."""
135
Doug Zongkerc9253822014-02-04 12:17:58 -0800136 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700137 if isinstance(input_file, zipfile.ZipFile):
138 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800139 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700140 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800141 try:
142 with open(path) as f:
143 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700144 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800145 if e.errno == errno.ENOENT:
146 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700147 d = {}
148 try:
Michael Runge6e836112014-04-15 17:40:21 -0700149 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700150 except KeyError:
151 # ok if misc_info.txt doesn't exist
152 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700153
Doug Zongker37974732010-09-16 17:44:38 -0700154 # backwards compatibility: These values used to be in their own
155 # files. Look for them, in case we're processing an old
156 # target_files zip.
157
Doug Zongker37974732010-09-16 17:44:38 -0700158 if "recovery_api_version" not in d:
159 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700160 d["recovery_api_version"] = read_helper(
161 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700162 except KeyError:
163 raise ValueError("can't find recovery API version in input target-files")
164
165 if "tool_extensions" not in d:
166 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800167 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700168 except KeyError:
169 # ok if extensions don't exist
170 pass
171
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800172 if "fstab_version" not in d:
173 d["fstab_version"] = "1"
174
Tao Bao84e75682015-07-19 02:38:53 -0700175 # A few properties are stored as links to the files in the out/ directory.
176 # It works fine with the build system. However, they are no longer available
177 # when (re)generating from target_files zip. If input_dir is not None, we
178 # are doing repacking. Redirect those properties to the actual files in the
179 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700180 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400181 # We carry a copy of file_contexts.bin under META/. If not available,
182 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700183 # to build images than the one running on device, such as when enabling
184 # system_root_image. In that case, we must have the one for image
185 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700186 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
187 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700188 if d.get("system_root_image") == "true":
189 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700190 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700191 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700192 if not os.path.exists(fc_config):
193 fc_config = None
194
195 if fc_config:
196 d["selinux_fc"] = fc_config
197
Tao Bao84e75682015-07-19 02:38:53 -0700198 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
199 if d.get("system_root_image") == "true":
200 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
201 d["ramdisk_fs_config"] = os.path.join(
202 input_dir, "META", "root_filesystem_config.txt")
203
Tao Baof54216f2016-03-29 15:12:37 -0700204 # Redirect {system,vendor}_base_fs_file.
205 if "system_base_fs_file" in d:
206 basename = os.path.basename(d["system_base_fs_file"])
207 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700208 if os.path.exists(system_base_fs_file):
209 d["system_base_fs_file"] = system_base_fs_file
210 else:
211 print "Warning: failed to find system base fs file: %s" % (
212 system_base_fs_file,)
213 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700214
215 if "vendor_base_fs_file" in d:
216 basename = os.path.basename(d["vendor_base_fs_file"])
217 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700218 if os.path.exists(vendor_base_fs_file):
219 d["vendor_base_fs_file"] = vendor_base_fs_file
220 else:
221 print "Warning: failed to find vendor base fs file: %s" % (
222 vendor_base_fs_file,)
223 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700224
Doug Zongker37974732010-09-16 17:44:38 -0700225 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800226 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700227 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700228 if not line:
229 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700230 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 if not value:
232 continue
Doug Zongker37974732010-09-16 17:44:38 -0700233 if name == "blocksize":
234 d[name] = value
235 else:
236 d[name + "_size"] = value
237 except KeyError:
238 pass
239
240 def makeint(key):
241 if key in d:
242 d[key] = int(d[key], 0)
243
244 makeint("recovery_api_version")
245 makeint("blocksize")
246 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700247 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700248 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700249 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700250 makeint("recovery_size")
251 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800252 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700253
Tianjie Xucfa86222016-03-07 16:31:19 -0800254 system_root_image = d.get("system_root_image", None) == "true"
255 if d.get("no_recovery", None) != "true":
256 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800257 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800258 recovery_fstab_path, system_root_image)
259 elif d.get("recovery_as_boot", None) == "true":
260 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
261 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
262 recovery_fstab_path, system_root_image)
263 else:
264 d["fstab"] = None
265
Doug Zongkerc9253822014-02-04 12:17:58 -0800266 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700267 return d
268
Doug Zongkerc9253822014-02-04 12:17:58 -0800269def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700270 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800271 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700272 except KeyError:
273 print "Warning: could not find SYSTEM/build.prop in %s" % zip
274 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700275 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700276
Michael Runge6e836112014-04-15 17:40:21 -0700277def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700278 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700279 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700280 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700281 if not line or line.startswith("#"):
282 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700283 if "=" in line:
284 name, value = line.split("=", 1)
285 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700286 return d
287
Tianjie Xucfa86222016-03-07 16:31:19 -0800288def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
289 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700290 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700291 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700292 self.mount_point = mount_point
293 self.fs_type = fs_type
294 self.device = device
295 self.length = length
296 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700297 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700298
299 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800300 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700301 except KeyError:
Tianjie Xucfa86222016-03-07 16:31:19 -0800302 print "Warning: could not find {}".format(recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700303 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700304
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800305 if fstab_version == 1:
306 d = {}
307 for line in data.split("\n"):
308 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700309 if not line or line.startswith("#"):
310 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800311 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700312 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800313 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800314 options = None
315 if len(pieces) >= 4:
316 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700317 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800318 if len(pieces) >= 5:
319 options = pieces[4]
320 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700321 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800322 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800323 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700324 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700325
Dan Albert8b72aef2015-03-23 19:13:21 -0700326 mount_point = pieces[0]
327 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800328 if options:
329 options = options.split(",")
330 for i in options:
331 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700332 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800333 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700334 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800335
Dan Albert8b72aef2015-03-23 19:13:21 -0700336 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
337 device=pieces[2], length=length,
338 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800339
340 elif fstab_version == 2:
341 d = {}
342 for line in data.split("\n"):
343 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700344 if not line or line.startswith("#"):
345 continue
Tao Bao548eb762015-06-10 12:32:41 -0700346 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800347 pieces = line.split()
348 if len(pieces) != 5:
349 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
350
351 # Ignore entries that are managed by vold
352 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700353 if "voldmanaged=" in options:
354 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800355
356 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700357 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800358 options = options.split(",")
359 for i in options:
360 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700361 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800362 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800363 # Ignore all unknown options in the unified fstab
364 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800365
Tao Bao548eb762015-06-10 12:32:41 -0700366 mount_flags = pieces[3]
367 # Honor the SELinux context if present.
368 context = None
369 for i in mount_flags.split(","):
370 if i.startswith("context="):
371 context = i
372
Dan Albert8b72aef2015-03-23 19:13:21 -0700373 mount_point = pieces[1]
374 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700375 device=pieces[0], length=length,
376 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800377
378 else:
379 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
380
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700381 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700382 # system. Other areas assume system is always at "/system" so point /system
383 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700384 if system_root_image:
385 assert not d.has_key("/system") and d.has_key("/")
386 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700387 return d
388
389
Doug Zongker37974732010-09-16 17:44:38 -0700390def DumpInfoDict(d):
391 for k, v in sorted(d.items()):
392 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700393
Dan Albert8b72aef2015-03-23 19:13:21 -0700394
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400395def AppendAVBSigningArgs(cmd):
396 """Append signing arguments for avbtool."""
397 keypath = OPTIONS.info_dict.get("board_avb_key_path", None)
398 algorithm = OPTIONS.info_dict.get("board_avb_algorithm", None)
399 if not keypath or not algorithm:
400 algorithm = "SHA256_RSA4096"
401 keypath = "external/avb/test/data/testkey_rsa4096.pem"
402 cmd.extend(["--key", keypath, "--algorithm", algorithm])
403
404
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700405def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800406 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700407 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700408
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700409 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800410 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
411 we are building a two-step special image (i.e. building a recovery image to
412 be loaded into /boot in two-step OTAs).
413
414 Return the image data, or None if sourcedir does not appear to contains files
415 for building the requested image.
416 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700417
418 def make_ramdisk():
419 ramdisk_img = tempfile.NamedTemporaryFile()
420
421 if os.access(fs_config_file, os.F_OK):
422 cmd = ["mkbootfs", "-f", fs_config_file,
423 os.path.join(sourcedir, "RAMDISK")]
424 else:
425 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
426 p1 = Run(cmd, stdout=subprocess.PIPE)
427 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
428
429 p2.wait()
430 p1.wait()
431 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
432 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
433
434 return ramdisk_img
435
436 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
437 return None
438
439 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700440 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700441
Doug Zongkerd5131602012-08-02 14:46:42 -0700442 if info_dict is None:
443 info_dict = OPTIONS.info_dict
444
Doug Zongkereef39442009-04-02 12:14:19 -0700445 img = tempfile.NamedTemporaryFile()
446
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700447 if has_ramdisk:
448 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700449
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800450 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
451 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
452
453 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700454
Benoit Fradina45a8682014-07-14 21:00:43 +0200455 fn = os.path.join(sourcedir, "second")
456 if os.access(fn, os.F_OK):
457 cmd.append("--second")
458 cmd.append(fn)
459
Doug Zongker171f1cd2009-06-15 22:36:37 -0700460 fn = os.path.join(sourcedir, "cmdline")
461 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700462 cmd.append("--cmdline")
463 cmd.append(open(fn).read().rstrip("\n"))
464
465 fn = os.path.join(sourcedir, "base")
466 if os.access(fn, os.F_OK):
467 cmd.append("--base")
468 cmd.append(open(fn).read().rstrip("\n"))
469
Ying Wang4de6b5b2010-08-25 14:29:34 -0700470 fn = os.path.join(sourcedir, "pagesize")
471 if os.access(fn, os.F_OK):
472 cmd.append("--pagesize")
473 cmd.append(open(fn).read().rstrip("\n"))
474
Doug Zongkerd5131602012-08-02 14:46:42 -0700475 args = info_dict.get("mkbootimg_args", None)
476 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700477 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700478
Sami Tolvanen3303d902016-03-15 16:49:30 +0000479 args = info_dict.get("mkbootimg_version_args", None)
480 if args and args.strip():
481 cmd.extend(shlex.split(args))
482
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700483 if has_ramdisk:
484 cmd.extend(["--ramdisk", ramdisk_img.name])
485
Tao Baod95e9fd2015-03-29 23:07:41 -0700486 img_unsigned = None
487 if info_dict.get("vboot", None):
488 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700489 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700490 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700491 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700492
493 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700494 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700495 assert p.returncode == 0, "mkbootimg of %s image failed" % (
496 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700497
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100498 if (info_dict.get("boot_signer", None) == "true" and
499 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800500 # Hard-code the path as "/boot" for two-step special recovery image (which
501 # will be loaded into /boot during the two-step OTA).
502 if two_step_image:
503 path = "/boot"
504 else:
505 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700506 cmd = [OPTIONS.boot_signer_path]
507 cmd.extend(OPTIONS.boot_signer_args)
508 cmd.extend([path, img.name,
509 info_dict["verity_key"] + ".pk8",
510 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700511 p = Run(cmd, stdout=subprocess.PIPE)
512 p.communicate()
513 assert p.returncode == 0, "boot_signer of %s image failed" % path
514
Tao Baod95e9fd2015-03-29 23:07:41 -0700515 # Sign the image if vboot is non-empty.
516 elif info_dict.get("vboot", None):
517 path = "/" + os.path.basename(sourcedir).lower()
518 img_keyblock = tempfile.NamedTemporaryFile()
519 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
520 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700521 info_dict["vboot_key"] + ".vbprivk",
522 info_dict["vboot_subkey"] + ".vbprivk",
523 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700524 img.name]
525 p = Run(cmd, stdout=subprocess.PIPE)
526 p.communicate()
527 assert p.returncode == 0, "vboot_signer of %s image failed" % path
528
Tao Baof3282b42015-04-01 11:21:55 -0700529 # Clean up the temp files.
530 img_unsigned.close()
531 img_keyblock.close()
532
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400533 # AVB: if enabled, calculate and add hash to boot.img.
Tao Baob31b94e2016-09-29 21:59:06 -0700534 if info_dict.get("board_avb_enable", None) == "true":
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400535 avbtool = os.getenv('AVBTOOL') or "avbtool"
Tao Baob31b94e2016-09-29 21:59:06 -0700536 part_size = info_dict.get("boot_size", None)
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400537 cmd = [avbtool, "add_hash_footer", "--image", img.name,
538 "--partition_size", str(part_size), "--partition_name", "boot"]
539 AppendAVBSigningArgs(cmd)
Tao Baob31b94e2016-09-29 21:59:06 -0700540 args = info_dict.get("board_avb_boot_add_hash_footer_args", None)
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400541 if args and args.strip():
542 cmd.extend(shlex.split(args))
543 p = Run(cmd, stdout=subprocess.PIPE)
544 p.communicate()
545 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
546 os.path.basename(OPTIONS.input_tmp))
David Zeuthend995f4b2016-01-29 16:59:17 -0500547
548 img.seek(os.SEEK_SET, 0)
549 data = img.read()
550
551 if has_ramdisk:
552 ramdisk_img.close()
553 img.close()
554
555 return data
556
557
Doug Zongkerd5131602012-08-02 14:46:42 -0700558def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800559 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700560 """Return a File object with the desired bootable image.
561
562 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
563 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
564 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700565
Doug Zongker55d93282011-01-25 17:03:34 -0800566 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
567 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700568 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800569 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700570
571 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
572 if os.path.exists(prebuilt_path):
573 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
574 return File.FromLocalFile(name, prebuilt_path)
575
576 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700577
578 if info_dict is None:
579 info_dict = OPTIONS.info_dict
580
581 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800582 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
583 # for recovery.
584 has_ramdisk = (info_dict.get("system_root_image") != "true" or
585 prebuilt_name != "boot.img" or
586 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700587
Doug Zongker6f1d0312014-08-22 08:07:12 -0700588 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400589 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
590 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800591 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700592 if data:
593 return File(name, data)
594 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800595
Doug Zongkereef39442009-04-02 12:14:19 -0700596
Doug Zongker75f17362009-12-08 13:46:44 -0800597def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800598 """Unzip the given archive into a temporary directory and return the name.
599
600 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
601 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
602
603 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
604 main file), open for reading.
605 """
Doug Zongkereef39442009-04-02 12:14:19 -0700606
607 tmp = tempfile.mkdtemp(prefix="targetfiles-")
608 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800609
610 def unzip_to_dir(filename, dirname):
611 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
612 if pattern is not None:
613 cmd.append(pattern)
614 p = Run(cmd, stdout=subprocess.PIPE)
615 p.communicate()
616 if p.returncode != 0:
617 raise ExternalError("failed to unzip input target-files \"%s\"" %
618 (filename,))
619
620 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
621 if m:
622 unzip_to_dir(m.group(1), tmp)
623 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
624 filename = m.group(1)
625 else:
626 unzip_to_dir(filename, tmp)
627
628 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700629
630
631def GetKeyPasswords(keylist):
632 """Given a list of keys, prompt the user to enter passwords for
633 those which require them. Return a {key: password} dict. password
634 will be None if the key has no password."""
635
Doug Zongker8ce7c252009-05-22 13:34:54 -0700636 no_passwords = []
637 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700638 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700639 devnull = open("/dev/null", "w+b")
640 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800641 # We don't need a password for things that aren't really keys.
642 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700643 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700644 continue
645
T.R. Fullhart37e10522013-03-18 10:31:26 -0700646 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700647 "-inform", "DER", "-nocrypt"],
648 stdin=devnull.fileno(),
649 stdout=devnull.fileno(),
650 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700651 p.communicate()
652 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700653 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700654 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700655 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700656 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
657 "-inform", "DER", "-passin", "pass:"],
658 stdin=devnull.fileno(),
659 stdout=devnull.fileno(),
660 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700661 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700662 if p.returncode == 0:
663 # Encrypted key with empty string as password.
664 key_passwords[k] = ''
665 elif stderr.startswith('Error decrypting key'):
666 # Definitely encrypted key.
667 # It would have said "Error reading key" if it didn't parse correctly.
668 need_passwords.append(k)
669 else:
670 # Potentially, a type of key that openssl doesn't understand.
671 # We'll let the routines in signapk.jar handle it.
672 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700673 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700674
T.R. Fullhart37e10522013-03-18 10:31:26 -0700675 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700676 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700677 return key_passwords
678
679
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800680def GetMinSdkVersion(apk_name):
681 """Get the minSdkVersion delared in the APK. This can be both a decimal number
682 (API Level) or a codename.
683 """
684
685 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
686 output, err = p.communicate()
687 if err:
688 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
689 % (p.returncode,))
690
691 for line in output.split("\n"):
692 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
693 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
694 if m:
695 return m.group(1)
696 raise ExternalError("No minSdkVersion returned by aapt")
697
698
699def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
700 """Get the minSdkVersion declared in the APK as a number (API Level). If
701 minSdkVersion is set to a codename, it is translated to a number using the
702 provided map.
703 """
704
705 version = GetMinSdkVersion(apk_name)
706 try:
707 return int(version)
708 except ValueError:
709 # Not a decimal number. Codename?
710 if version in codename_to_api_level_map:
711 return codename_to_api_level_map[version]
712 else:
713 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
714 % (version, codename_to_api_level_map))
715
716
717def SignFile(input_name, output_name, key, password, min_api_level=None,
718 codename_to_api_level_map=dict(),
719 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700720 """Sign the input_name zip/jar/apk, producing output_name. Use the
721 given key and password (the latter may be None if the key does not
722 have a password.
723
Doug Zongker951495f2009-08-14 12:44:19 -0700724 If whole_file is true, use the "-w" option to SignApk to embed a
725 signature that covers the whole file in the archive comment of the
726 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800727
728 min_api_level is the API Level (int) of the oldest platform this file may end
729 up on. If not specified for an APK, the API Level is obtained by interpreting
730 the minSdkVersion attribute of the APK's AndroidManifest.xml.
731
732 codename_to_api_level_map is needed to translate the codename which may be
733 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700734 """
Doug Zongker951495f2009-08-14 12:44:19 -0700735
Alex Klyubin9667b182015-12-10 13:38:50 -0800736 java_library_path = os.path.join(
737 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
738
Tao Baoe95540e2016-11-08 12:08:53 -0800739 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
740 ["-Djava.library.path=" + java_library_path,
741 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
742 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700743 if whole_file:
744 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800745
746 min_sdk_version = min_api_level
747 if min_sdk_version is None:
748 if not whole_file:
749 min_sdk_version = GetMinSdkVersionInt(
750 input_name, codename_to_api_level_map)
751 if min_sdk_version is not None:
752 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
753
T.R. Fullhart37e10522013-03-18 10:31:26 -0700754 cmd.extend([key + OPTIONS.public_key_suffix,
755 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800756 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700757
758 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700759 if password is not None:
760 password += "\n"
761 p.communicate(password)
762 if p.returncode != 0:
763 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
764
Doug Zongkereef39442009-04-02 12:14:19 -0700765
Doug Zongker37974732010-09-16 17:44:38 -0700766def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700767 """Check the data string passed against the max size limit, if
768 any, for the given target. Raise exception if the data is too big.
769 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700770
Dan Albert8b72aef2015-03-23 19:13:21 -0700771 if target.endswith(".img"):
772 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700773 mount_point = "/" + target
774
Ying Wangf8824af2014-06-03 14:07:27 -0700775 fs_type = None
776 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700777 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700778 if mount_point == "/userdata":
779 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700780 p = info_dict["fstab"][mount_point]
781 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800782 device = p.device
783 if "/" in device:
784 device = device[device.rfind("/")+1:]
785 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700786 if not fs_type or not limit:
787 return
Doug Zongkereef39442009-04-02 12:14:19 -0700788
Andrew Boie0f9aec82012-02-14 09:32:52 -0800789 size = len(data)
790 pct = float(size) * 100.0 / limit
791 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
792 if pct >= 99.0:
793 raise ExternalError(msg)
794 elif pct >= 95.0:
795 print
796 print " WARNING: ", msg
797 print
798 elif OPTIONS.verbose:
799 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700800
801
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800802def ReadApkCerts(tf_zip):
803 """Given a target_files ZipFile, parse the META/apkcerts.txt file
804 and return a {package: cert} dict."""
805 certmap = {}
806 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
807 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700808 if not line:
809 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800810 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
811 r'private_key="(.*)"$', line)
812 if m:
813 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700814 public_key_suffix_len = len(OPTIONS.public_key_suffix)
815 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800816 if cert in SPECIAL_CERT_STRINGS and not privkey:
817 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700818 elif (cert.endswith(OPTIONS.public_key_suffix) and
819 privkey.endswith(OPTIONS.private_key_suffix) and
820 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
821 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800822 else:
823 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
824 return certmap
825
826
Doug Zongkereef39442009-04-02 12:14:19 -0700827COMMON_DOCSTRING = """
828 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700829 Prepend <dir>/bin to the list of places to search for binaries
830 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700831
Doug Zongker05d3dea2009-06-22 11:32:31 -0700832 -s (--device_specific) <file>
833 Path to the python module containing device-specific
834 releasetools code.
835
Doug Zongker8bec09e2009-11-30 15:37:14 -0800836 -x (--extra) <key=value>
837 Add a key/value pair to the 'extras' dict, which device-specific
838 extension code may look at.
839
Doug Zongkereef39442009-04-02 12:14:19 -0700840 -v (--verbose)
841 Show command lines being executed.
842
843 -h (--help)
844 Display this usage message and exit.
845"""
846
847def Usage(docstring):
848 print docstring.rstrip("\n")
849 print COMMON_DOCSTRING
850
851
852def ParseOptions(argv,
853 docstring,
854 extra_opts="", extra_long_opts=(),
855 extra_option_handler=None):
856 """Parse the options in argv and return any arguments that aren't
857 flags. docstring is the calling module's docstring, to be displayed
858 for errors and -h. extra_opts and extra_long_opts are for flags
859 defined by the caller, which are processed by passing them to
860 extra_option_handler."""
861
862 try:
863 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800864 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800865 ["help", "verbose", "path=", "signapk_path=",
866 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700867 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700868 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
869 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800870 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700871 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700872 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700873 Usage(docstring)
874 print "**", str(err), "**"
875 sys.exit(2)
876
Doug Zongkereef39442009-04-02 12:14:19 -0700877 for o, a in opts:
878 if o in ("-h", "--help"):
879 Usage(docstring)
880 sys.exit()
881 elif o in ("-v", "--verbose"):
882 OPTIONS.verbose = True
883 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700884 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700885 elif o in ("--signapk_path",):
886 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800887 elif o in ("--signapk_shared_library_path",):
888 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700889 elif o in ("--extra_signapk_args",):
890 OPTIONS.extra_signapk_args = shlex.split(a)
891 elif o in ("--java_path",):
892 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700893 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800894 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700895 elif o in ("--public_key_suffix",):
896 OPTIONS.public_key_suffix = a
897 elif o in ("--private_key_suffix",):
898 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800899 elif o in ("--boot_signer_path",):
900 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700901 elif o in ("--boot_signer_args",):
902 OPTIONS.boot_signer_args = shlex.split(a)
903 elif o in ("--verity_signer_path",):
904 OPTIONS.verity_signer_path = a
905 elif o in ("--verity_signer_args",):
906 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700907 elif o in ("-s", "--device_specific"):
908 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800909 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800910 key, value = a.split("=", 1)
911 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700912 else:
913 if extra_option_handler is None or not extra_option_handler(o, a):
914 assert False, "unknown option \"%s\"" % (o,)
915
Doug Zongker85448772014-09-09 14:59:20 -0700916 if OPTIONS.search_path:
917 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
918 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700919
920 return args
921
922
Doug Zongkerfc44a512014-08-26 13:10:25 -0700923def MakeTempFile(prefix=None, suffix=None):
924 """Make a temp file and add it to the list of things to be deleted
925 when Cleanup() is called. Return the filename."""
926 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
927 os.close(fd)
928 OPTIONS.tempfiles.append(fn)
929 return fn
930
931
Doug Zongkereef39442009-04-02 12:14:19 -0700932def Cleanup():
933 for i in OPTIONS.tempfiles:
934 if os.path.isdir(i):
935 shutil.rmtree(i)
936 else:
937 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700938
939
940class PasswordManager(object):
941 def __init__(self):
942 self.editor = os.getenv("EDITOR", None)
943 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
944
945 def GetPasswords(self, items):
946 """Get passwords corresponding to each string in 'items',
947 returning a dict. (The dict may have keys in addition to the
948 values in 'items'.)
949
950 Uses the passwords in $ANDROID_PW_FILE if available, letting the
951 user edit that file to add more needed passwords. If no editor is
952 available, or $ANDROID_PW_FILE isn't define, prompts the user
953 interactively in the ordinary way.
954 """
955
956 current = self.ReadFile()
957
958 first = True
959 while True:
960 missing = []
961 for i in items:
962 if i not in current or not current[i]:
963 missing.append(i)
964 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700965 if not missing:
966 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700967
968 for i in missing:
969 current[i] = ""
970
971 if not first:
972 print "key file %s still missing some passwords." % (self.pwfile,)
973 answer = raw_input("try to edit again? [y]> ").strip()
974 if answer and answer[0] not in 'yY':
975 raise RuntimeError("key passwords unavailable")
976 first = False
977
978 current = self.UpdateAndReadFile(current)
979
Dan Albert8b72aef2015-03-23 19:13:21 -0700980 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700981 """Prompt the user to enter a value (password) for each key in
982 'current' whose value is fales. Returns a new dict with all the
983 values.
984 """
985 result = {}
986 for k, v in sorted(current.iteritems()):
987 if v:
988 result[k] = v
989 else:
990 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700991 result[k] = getpass.getpass(
992 "Enter password for %s key> " % k).strip()
993 if result[k]:
994 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700995 return result
996
997 def UpdateAndReadFile(self, current):
998 if not self.editor or not self.pwfile:
999 return self.PromptResult(current)
1000
1001 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001002 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001003 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1004 f.write("# (Additional spaces are harmless.)\n\n")
1005
1006 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001007 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1008 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001009 f.write("[[[ %s ]]] %s\n" % (v, k))
1010 if not v and first_line is None:
1011 # position cursor on first line with no password.
1012 first_line = i + 4
1013 f.close()
1014
1015 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1016 _, _ = p.communicate()
1017
1018 return self.ReadFile()
1019
1020 def ReadFile(self):
1021 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001022 if self.pwfile is None:
1023 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001024 try:
1025 f = open(self.pwfile, "r")
1026 for line in f:
1027 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001028 if not line or line[0] == '#':
1029 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001030 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1031 if not m:
1032 print "failed to parse password file: ", line
1033 else:
1034 result[m.group(2)] = m.group(1)
1035 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001036 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001037 if e.errno != errno.ENOENT:
1038 print "error reading password file: ", str(e)
1039 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001040
1041
Dan Albert8e0178d2015-01-27 15:53:15 -08001042def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1043 compress_type=None):
1044 import datetime
1045
1046 # http://b/18015246
1047 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1048 # for files larger than 2GiB. We can work around this by adjusting their
1049 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1050 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1051 # it isn't clear to me exactly what circumstances cause this).
1052 # `zipfile.write()` must be used directly to work around this.
1053 #
1054 # This mess can be avoided if we port to python3.
1055 saved_zip64_limit = zipfile.ZIP64_LIMIT
1056 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1057
1058 if compress_type is None:
1059 compress_type = zip_file.compression
1060 if arcname is None:
1061 arcname = filename
1062
1063 saved_stat = os.stat(filename)
1064
1065 try:
1066 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1067 # file to be zipped and reset it when we're done.
1068 os.chmod(filename, perms)
1069
1070 # Use a fixed timestamp so the output is repeatable.
1071 epoch = datetime.datetime.fromtimestamp(0)
1072 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1073 os.utime(filename, (timestamp, timestamp))
1074
1075 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1076 finally:
1077 os.chmod(filename, saved_stat.st_mode)
1078 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1079 zipfile.ZIP64_LIMIT = saved_zip64_limit
1080
1081
Tao Bao58c1b962015-05-20 09:32:18 -07001082def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001083 compress_type=None):
1084 """Wrap zipfile.writestr() function to work around the zip64 limit.
1085
1086 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1087 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1088 when calling crc32(bytes).
1089
1090 But it still works fine to write a shorter string into a large zip file.
1091 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1092 when we know the string won't be too long.
1093 """
1094
1095 saved_zip64_limit = zipfile.ZIP64_LIMIT
1096 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1097
1098 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1099 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001100 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001101 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001102 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001103 else:
Tao Baof3282b42015-04-01 11:21:55 -07001104 zinfo = zinfo_or_arcname
1105
1106 # If compress_type is given, it overrides the value in zinfo.
1107 if compress_type is not None:
1108 zinfo.compress_type = compress_type
1109
Tao Bao58c1b962015-05-20 09:32:18 -07001110 # If perms is given, it has a priority.
1111 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001112 # If perms doesn't set the file type, mark it as a regular file.
1113 if perms & 0o770000 == 0:
1114 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001115 zinfo.external_attr = perms << 16
1116
Tao Baof3282b42015-04-01 11:21:55 -07001117 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001118 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1119
Dan Albert8b72aef2015-03-23 19:13:21 -07001120 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001121 zipfile.ZIP64_LIMIT = saved_zip64_limit
1122
1123
1124def ZipClose(zip_file):
1125 # http://b/18015246
1126 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1127 # central directory.
1128 saved_zip64_limit = zipfile.ZIP64_LIMIT
1129 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1130
1131 zip_file.close()
1132
1133 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001134
1135
1136class DeviceSpecificParams(object):
1137 module = None
1138 def __init__(self, **kwargs):
1139 """Keyword arguments to the constructor become attributes of this
1140 object, which is passed to all functions in the device-specific
1141 module."""
1142 for k, v in kwargs.iteritems():
1143 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001144 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001145
1146 if self.module is None:
1147 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001148 if not path:
1149 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001150 try:
1151 if os.path.isdir(path):
1152 info = imp.find_module("releasetools", [path])
1153 else:
1154 d, f = os.path.split(path)
1155 b, x = os.path.splitext(f)
1156 if x == ".py":
1157 f = b
1158 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001159 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001160 self.module = imp.load_module("device_specific", *info)
1161 except ImportError:
1162 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001163
1164 def _DoCall(self, function_name, *args, **kwargs):
1165 """Call the named function in the device-specific module, passing
1166 the given args and kwargs. The first argument to the call will be
1167 the DeviceSpecific object itself. If there is no module, or the
1168 module does not define the function, return the value of the
1169 'default' kwarg (which itself defaults to None)."""
1170 if self.module is None or not hasattr(self.module, function_name):
1171 return kwargs.get("default", None)
1172 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1173
1174 def FullOTA_Assertions(self):
1175 """Called after emitting the block of assertions at the top of a
1176 full OTA package. Implementations can add whatever additional
1177 assertions they like."""
1178 return self._DoCall("FullOTA_Assertions")
1179
Doug Zongkere5ff5902012-01-17 10:55:37 -08001180 def FullOTA_InstallBegin(self):
1181 """Called at the start of full OTA installation."""
1182 return self._DoCall("FullOTA_InstallBegin")
1183
Doug Zongker05d3dea2009-06-22 11:32:31 -07001184 def FullOTA_InstallEnd(self):
1185 """Called at the end of full OTA installation; typically this is
1186 used to install the image for the device's baseband processor."""
1187 return self._DoCall("FullOTA_InstallEnd")
1188
1189 def IncrementalOTA_Assertions(self):
1190 """Called after emitting the block of assertions at the top of an
1191 incremental OTA package. Implementations can add whatever
1192 additional assertions they like."""
1193 return self._DoCall("IncrementalOTA_Assertions")
1194
Doug Zongkere5ff5902012-01-17 10:55:37 -08001195 def IncrementalOTA_VerifyBegin(self):
1196 """Called at the start of the verification phase of incremental
1197 OTA installation; additional checks can be placed here to abort
1198 the script before any changes are made."""
1199 return self._DoCall("IncrementalOTA_VerifyBegin")
1200
Doug Zongker05d3dea2009-06-22 11:32:31 -07001201 def IncrementalOTA_VerifyEnd(self):
1202 """Called at the end of the verification phase of incremental OTA
1203 installation; additional checks can be placed here to abort the
1204 script before any changes are made."""
1205 return self._DoCall("IncrementalOTA_VerifyEnd")
1206
Doug Zongkere5ff5902012-01-17 10:55:37 -08001207 def IncrementalOTA_InstallBegin(self):
1208 """Called at the start of incremental OTA installation (after
1209 verification is complete)."""
1210 return self._DoCall("IncrementalOTA_InstallBegin")
1211
Doug Zongker05d3dea2009-06-22 11:32:31 -07001212 def IncrementalOTA_InstallEnd(self):
1213 """Called at the end of incremental OTA installation; typically
1214 this is used to install the image for the device's baseband
1215 processor."""
1216 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001217
Tao Bao9bc6bb22015-11-09 16:58:28 -08001218 def VerifyOTA_Assertions(self):
1219 return self._DoCall("VerifyOTA_Assertions")
1220
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001221class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001222 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001223 self.name = name
1224 self.data = data
1225 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001226 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001227 self.sha1 = sha1(data).hexdigest()
1228
1229 @classmethod
1230 def FromLocalFile(cls, name, diskname):
1231 f = open(diskname, "rb")
1232 data = f.read()
1233 f.close()
1234 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001235
1236 def WriteToTemp(self):
1237 t = tempfile.NamedTemporaryFile()
1238 t.write(self.data)
1239 t.flush()
1240 return t
1241
Geremy Condra36bd3652014-02-06 19:45:10 -08001242 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001243 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001244
1245DIFF_PROGRAM_BY_EXT = {
1246 ".gz" : "imgdiff",
1247 ".zip" : ["imgdiff", "-z"],
1248 ".jar" : ["imgdiff", "-z"],
1249 ".apk" : ["imgdiff", "-z"],
1250 ".img" : "imgdiff",
1251 }
1252
1253class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001254 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001255 self.tf = tf
1256 self.sf = sf
1257 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001258 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001259
1260 def ComputePatch(self):
1261 """Compute the patch (as a string of data) needed to turn sf into
1262 tf. Returns the same tuple as GetPatch()."""
1263
1264 tf = self.tf
1265 sf = self.sf
1266
Doug Zongker24cd2802012-08-14 16:36:15 -07001267 if self.diff_program:
1268 diff_program = self.diff_program
1269 else:
1270 ext = os.path.splitext(tf.name)[1]
1271 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001272
1273 ttemp = tf.WriteToTemp()
1274 stemp = sf.WriteToTemp()
1275
1276 ext = os.path.splitext(tf.name)[1]
1277
1278 try:
1279 ptemp = tempfile.NamedTemporaryFile()
1280 if isinstance(diff_program, list):
1281 cmd = copy.copy(diff_program)
1282 else:
1283 cmd = [diff_program]
1284 cmd.append(stemp.name)
1285 cmd.append(ttemp.name)
1286 cmd.append(ptemp.name)
1287 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001288 err = []
1289 def run():
1290 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001291 if e:
1292 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001293 th = threading.Thread(target=run)
1294 th.start()
1295 th.join(timeout=300) # 5 mins
1296 if th.is_alive():
1297 print "WARNING: diff command timed out"
1298 p.terminate()
1299 th.join(5)
1300 if th.is_alive():
1301 p.kill()
1302 th.join()
1303
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001304 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001305 print "WARNING: failure running %s:\n%s\n" % (
1306 diff_program, "".join(err))
1307 self.patch = None
1308 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001309 diff = ptemp.read()
1310 finally:
1311 ptemp.close()
1312 stemp.close()
1313 ttemp.close()
1314
1315 self.patch = diff
1316 return self.tf, self.sf, self.patch
1317
1318
1319 def GetPatch(self):
1320 """Return a tuple (target_file, source_file, patch_data).
1321 patch_data may be None if ComputePatch hasn't been called, or if
1322 computing the patch failed."""
1323 return self.tf, self.sf, self.patch
1324
1325
1326def ComputeDifferences(diffs):
1327 """Call ComputePatch on all the Difference objects in 'diffs'."""
1328 print len(diffs), "diffs to compute"
1329
1330 # Do the largest files first, to try and reduce the long-pole effect.
1331 by_size = [(i.tf.size, i) for i in diffs]
1332 by_size.sort(reverse=True)
1333 by_size = [i[1] for i in by_size]
1334
1335 lock = threading.Lock()
1336 diff_iter = iter(by_size) # accessed under lock
1337
1338 def worker():
1339 try:
1340 lock.acquire()
1341 for d in diff_iter:
1342 lock.release()
1343 start = time.time()
1344 d.ComputePatch()
1345 dur = time.time() - start
1346 lock.acquire()
1347
1348 tf, sf, patch = d.GetPatch()
1349 if sf.name == tf.name:
1350 name = tf.name
1351 else:
1352 name = "%s (%s)" % (tf.name, sf.name)
1353 if patch is None:
1354 print "patching failed! %s" % (name,)
1355 else:
1356 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1357 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1358 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001359 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001360 print e
1361 raise
1362
1363 # start worker threads; wait for them all to finish.
1364 threads = [threading.Thread(target=worker)
1365 for i in range(OPTIONS.worker_threads)]
1366 for th in threads:
1367 th.start()
1368 while threads:
1369 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001370
1371
Dan Albert8b72aef2015-03-23 19:13:21 -07001372class BlockDifference(object):
1373 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001374 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001375 self.tgt = tgt
1376 self.src = src
1377 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001378 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001379 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001380
Tao Baodd2a5892015-03-12 12:32:37 -07001381 if version is None:
1382 version = 1
1383 if OPTIONS.info_dict:
1384 version = max(
1385 int(i) for i in
1386 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1387 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001388
1389 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001390 version=self.version,
1391 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001392 tmpdir = tempfile.mkdtemp()
1393 OPTIONS.tempfiles.append(tmpdir)
1394 self.path = os.path.join(tmpdir, partition)
1395 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001396 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001397 self.touched_src_ranges = b.touched_src_ranges
1398 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001399
Tao Baoaac4ad52015-10-16 15:26:34 -07001400 if src is None:
1401 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1402 else:
1403 _, self.device = GetTypeAndDevice("/" + partition,
1404 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001405
Tao Baod8d14be2016-02-04 14:26:02 -08001406 @property
1407 def required_cache(self):
1408 return self._required_cache
1409
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001410 def WriteScript(self, script, output_zip, progress=None):
1411 if not self.src:
1412 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001413 script.Print("Patching %s image unconditionally..." % (self.partition,))
1414 else:
1415 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001416
Dan Albert8b72aef2015-03-23 19:13:21 -07001417 if progress:
1418 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001419 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001420 if OPTIONS.verify:
1421 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001422
Tao Bao9bc6bb22015-11-09 16:58:28 -08001423 def WriteStrictVerifyScript(self, script):
1424 """Verify all the blocks in the care_map, including clobbered blocks.
1425
1426 This differs from the WriteVerifyScript() function: a) it prints different
1427 error messages; b) it doesn't allow half-way updated images to pass the
1428 verification."""
1429
1430 partition = self.partition
1431 script.Print("Verifying %s..." % (partition,))
1432 ranges = self.tgt.care_map
1433 ranges_str = ranges.to_string_raw()
1434 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1435 'ui_print(" Verified.") || '
1436 'ui_print("\\"%s\\" has unexpected contents.");' % (
1437 self.device, ranges_str,
1438 self.tgt.TotalSha1(include_clobbered_blocks=True),
1439 self.device))
1440 script.AppendExtra("")
1441
Tao Baod522bdc2016-04-12 15:53:16 -07001442 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001443 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001444
1445 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001446 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001447 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001448
1449 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001450 else:
Tao Baod522bdc2016-04-12 15:53:16 -07001451 if touched_blocks_only and self.version >= 3:
1452 ranges = self.touched_src_ranges
1453 expected_sha1 = self.touched_src_sha1
1454 else:
1455 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1456 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001457
1458 # No blocks to be checked, skipping.
1459 if not ranges:
1460 return
1461
Tao Bao5ece99d2015-05-12 11:42:31 -07001462 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001463 if self.version >= 4:
1464 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1465 'block_image_verify("%s", '
1466 'package_extract_file("%s.transfer.list"), '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001467 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baod522bdc2016-04-12 15:53:16 -07001468 self.device, ranges_str, expected_sha1,
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001469 self.device, partition, partition, partition))
1470 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001471 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1472 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001473 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001474 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baod522bdc2016-04-12 15:53:16 -07001475 self.device, ranges_str, expected_sha1,
Sami Tolvanene09d0962015-04-24 11:54:01 +01001476 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001477 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001478 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001479 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001480 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001481 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001482
Tianjie Xufc3422a2015-12-15 11:53:59 -08001483 if self.version >= 4:
1484
1485 # Bug: 21124327
1486 # When generating incrementals for the system and vendor partitions in
1487 # version 4 or newer, explicitly check the first block (which contains
1488 # the superblock) of the partition to see if it's what we expect. If
1489 # this check fails, give an explicit log message about the partition
1490 # having been remounted R/W (the most likely explanation).
1491 if self.check_first_block:
1492 script.AppendExtra('check_first_block("%s");' % (self.device,))
1493
1494 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001495 if partition == "system":
1496 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1497 else:
1498 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001499 script.AppendExtra((
1500 'ifelse (block_image_recover("{device}", "{ranges}") && '
1501 'block_image_verify("{device}", '
1502 'package_extract_file("{partition}.transfer.list"), '
1503 '"{partition}.new.dat", "{partition}.patch.dat"), '
1504 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001505 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001506 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001507 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001508
Tao Baodd2a5892015-03-12 12:32:37 -07001509 # Abort the OTA update. Note that the incremental OTA cannot be applied
1510 # even if it may match the checksum of the target partition.
1511 # a) If version < 3, operations like move and erase will make changes
1512 # unconditionally and damage the partition.
1513 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001514 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001515 if partition == "system":
1516 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1517 else:
1518 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1519 script.AppendExtra((
1520 'abort("E%d: %s partition has unexpected contents");\n'
1521 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001522
Tao Bao5fcaaef2015-06-01 13:40:49 -07001523 def _WritePostInstallVerifyScript(self, script):
1524 partition = self.partition
1525 script.Print('Verifying the updated %s image...' % (partition,))
1526 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1527 ranges = self.tgt.care_map
1528 ranges_str = ranges.to_string_raw()
1529 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1530 self.device, ranges_str,
1531 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001532
1533 # Bug: 20881595
1534 # Verify that extended blocks are really zeroed out.
1535 if self.tgt.extended:
1536 ranges_str = self.tgt.extended.to_string_raw()
1537 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1538 self.device, ranges_str,
1539 self._HashZeroBlocks(self.tgt.extended.size())))
1540 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001541 if partition == "system":
1542 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1543 else:
1544 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001545 script.AppendExtra(
1546 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001547 ' abort("E%d: %s partition has unexpected non-zero contents after '
1548 'OTA update");\n'
1549 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001550 else:
1551 script.Print('Verified the updated %s image.' % (partition,))
1552
Tianjie Xu209db462016-05-24 17:34:52 -07001553 if partition == "system":
1554 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1555 else:
1556 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1557
Tao Bao5fcaaef2015-06-01 13:40:49 -07001558 script.AppendExtra(
1559 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001560 ' abort("E%d: %s partition has unexpected contents after OTA '
1561 'update");\n'
1562 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001563
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001564 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001565 ZipWrite(output_zip,
1566 '{}.transfer.list'.format(self.path),
1567 '{}.transfer.list'.format(self.partition))
1568 ZipWrite(output_zip,
1569 '{}.new.dat'.format(self.path),
1570 '{}.new.dat'.format(self.partition))
1571 ZipWrite(output_zip,
1572 '{}.patch.dat'.format(self.path),
1573 '{}.patch.dat'.format(self.partition),
1574 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001575
Tianjie Xu209db462016-05-24 17:34:52 -07001576 if self.partition == "system":
1577 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1578 else:
1579 code = ErrorCode.VENDOR_UPDATE_FAILURE
1580
Dan Albert8e0178d2015-01-27 15:53:15 -08001581 call = ('block_image_update("{device}", '
1582 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub2deb222016-03-25 15:01:33 -07001583 '"{partition}.new.dat", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001584 ' abort("E{code}: Failed to update {partition} image.");'.format(
1585 device=self.device, partition=self.partition, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001586 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001587
Dan Albert8b72aef2015-03-23 19:13:21 -07001588 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001589 data = source.ReadRangeSet(ranges)
1590 ctx = sha1()
1591
1592 for p in data:
1593 ctx.update(p)
1594
1595 return ctx.hexdigest()
1596
Tao Baoe9b61912015-07-09 17:37:49 -07001597 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1598 """Return the hash value for all zero blocks."""
1599 zero_block = '\x00' * 4096
1600 ctx = sha1()
1601 for _ in range(num_blocks):
1602 ctx.update(zero_block)
1603
1604 return ctx.hexdigest()
1605
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001606
1607DataImage = blockimgdiff.DataImage
1608
Doug Zongker96a57e72010-09-26 14:57:41 -07001609# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001610PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001611 "ext4": "EMMC",
1612 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001613 "f2fs": "EMMC",
1614 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001615}
Doug Zongker96a57e72010-09-26 14:57:41 -07001616
1617def GetTypeAndDevice(mount_point, info):
1618 fstab = info["fstab"]
1619 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001620 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1621 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001622 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001623 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001624
1625
1626def ParseCertificate(data):
1627 """Parse a PEM-format certificate."""
1628 cert = []
1629 save = False
1630 for line in data.split("\n"):
1631 if "--END CERTIFICATE--" in line:
1632 break
1633 if save:
1634 cert.append(line)
1635 if "--BEGIN CERTIFICATE--" in line:
1636 save = True
1637 cert = "".join(cert).decode('base64')
1638 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001639
Doug Zongker412c02f2014-02-13 10:58:24 -08001640def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1641 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001642 """Generate a binary patch that creates the recovery image starting
1643 with the boot image. (Most of the space in these images is just the
1644 kernel, which is identical for the two, so the resulting patch
1645 should be efficient.) Add it to the output zip, along with a shell
1646 script that is run from init.rc on first boot to actually do the
1647 patching and install the new recovery image.
1648
1649 recovery_img and boot_img should be File objects for the
1650 corresponding images. info should be the dictionary returned by
1651 common.LoadInfoDict() on the input target_files.
1652 """
1653
Doug Zongker412c02f2014-02-13 10:58:24 -08001654 if info_dict is None:
1655 info_dict = OPTIONS.info_dict
1656
Tao Baof2cffbd2015-07-22 12:33:18 -07001657 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001658 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001659
Tao Baof2cffbd2015-07-22 12:33:18 -07001660 if full_recovery_image:
1661 output_sink("etc/recovery.img", recovery_img.data)
1662
1663 else:
1664 diff_program = ["imgdiff"]
1665 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1666 if os.path.exists(path):
1667 diff_program.append("-b")
1668 diff_program.append(path)
1669 bonus_args = "-b /system/etc/recovery-resource.dat"
1670 else:
1671 bonus_args = ""
1672
1673 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1674 _, _, patch = d.ComputePatch()
1675 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001676
Dan Albertebb19aa2015-03-27 19:11:53 -07001677 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001678 # The following GetTypeAndDevice()s need to use the path in the target
1679 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001680 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1681 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1682 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001683 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001684
Tao Baof2cffbd2015-07-22 12:33:18 -07001685 if full_recovery_image:
1686 sh = """#!/system/bin/sh
1687if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1688 applypatch /system/etc/recovery.img %(type)s:%(device)s %(sha1)s %(size)d && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1689else
1690 log -t recovery "Recovery image already installed"
1691fi
1692""" % {'type': recovery_type,
1693 'device': recovery_device,
1694 'sha1': recovery_img.sha1,
1695 'size': recovery_img.size}
1696 else:
1697 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001698if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1699 applypatch %(bonus_args)s %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1700else
1701 log -t recovery "Recovery image already installed"
1702fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001703""" % {'boot_size': boot_img.size,
1704 'boot_sha1': boot_img.sha1,
1705 'recovery_size': recovery_img.size,
1706 'recovery_sha1': recovery_img.sha1,
1707 'boot_type': boot_type,
1708 'boot_device': boot_device,
1709 'recovery_type': recovery_type,
1710 'recovery_device': recovery_device,
1711 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001712
1713 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001714 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001715 # target-files expects it to be, and put it there.
1716 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001717 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001718 if system_root_image:
1719 init_rc_dir = os.path.join(input_dir, "ROOT")
1720 else:
1721 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001722 init_rc_files = os.listdir(init_rc_dir)
1723 for init_rc_file in init_rc_files:
1724 if (not init_rc_file.startswith('init.') or
1725 not init_rc_file.endswith('.rc')):
1726 continue
1727
1728 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001729 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001730 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001731 if m:
1732 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001733 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001734 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001735
1736 if found:
1737 break
1738
1739 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001740
1741 output_sink(sh_location, sh)