blob: ee0cdf97a711eff47ab93c82200436c6f2422d32 [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
Dan Albert8b72aef2015-03-23 19:13:21 -070033import rangelib
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Tao Baof3282b42015-04-01 11:21:55 -070035from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036
Doug Zongkereef39442009-04-02 12:14:19 -070037
Dan Albert8b72aef2015-03-23 19:13:21 -070038class Options(object):
39 def __init__(self):
40 platform_search_path = {
41 "linux2": "out/host/linux-x86",
42 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070043 }
Doug Zongker85448772014-09-09 14:59:20 -070044
Dan Albert8b72aef2015-03-23 19:13:21 -070045 self.search_path = platform_search_path.get(sys.platform, None)
46 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080047 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.extra_signapk_args = []
49 self.java_path = "java" # Use the one on the path by default.
50 self.java_args = "-Xmx2048m" # JVM Args
51 self.public_key_suffix = ".x509.pem"
52 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070053 # use otatools built boot_signer by default
54 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070055 self.boot_signer_args = []
56 self.verity_signer_path = None
57 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070058 self.verbose = False
59 self.tempfiles = []
60 self.device_specific = None
61 self.extras = {}
62 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070063 self.source_info_dict = None
64 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070065 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070066 # Stash size cannot exceed cache_size * threshold.
67 self.cache_size = None
68 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070069
70
71OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070072
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080073
74# Values for "certificate" in apkcerts that mean special things.
75SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
76
77
Dan Albert8b72aef2015-03-23 19:13:21 -070078class ExternalError(RuntimeError):
79 pass
Doug Zongkereef39442009-04-02 12:14:19 -070080
81
82def Run(args, **kwargs):
83 """Create and return a subprocess.Popen object, printing the command
84 line on the terminal if -v was specified."""
85 if OPTIONS.verbose:
86 print " running: ", " ".join(args)
87 return subprocess.Popen(args, **kwargs)
88
89
Ying Wang7e6d4e42010-12-13 16:25:36 -080090def CloseInheritedPipes():
91 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
92 before doing other work."""
93 if platform.system() != "Darwin":
94 return
95 for d in range(3, 1025):
96 try:
97 stat = os.fstat(d)
98 if stat is not None:
99 pipebit = stat[0] & 0x1000
100 if pipebit != 0:
101 os.close(d)
102 except OSError:
103 pass
104
105
Tao Bao2c15d9e2015-07-09 11:51:16 -0700106def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700107 """Read and parse the META/misc_info.txt key/value pairs from the
108 input target files and return a dict."""
109
Doug Zongkerc9253822014-02-04 12:17:58 -0800110 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700111 if isinstance(input_file, zipfile.ZipFile):
112 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800113 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700114 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800115 try:
116 with open(path) as f:
117 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700118 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800119 if e.errno == errno.ENOENT:
120 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700121 d = {}
122 try:
Michael Runge6e836112014-04-15 17:40:21 -0700123 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700124 except KeyError:
125 # ok if misc_info.txt doesn't exist
126 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700127
Doug Zongker37974732010-09-16 17:44:38 -0700128 # backwards compatibility: These values used to be in their own
129 # files. Look for them, in case we're processing an old
130 # target_files zip.
131
132 if "mkyaffs2_extra_flags" not in d:
133 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700134 d["mkyaffs2_extra_flags"] = read_helper(
135 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700136 except KeyError:
137 # ok if flags don't exist
138 pass
139
140 if "recovery_api_version" not in d:
141 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700142 d["recovery_api_version"] = read_helper(
143 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700144 except KeyError:
145 raise ValueError("can't find recovery API version in input target-files")
146
147 if "tool_extensions" not in d:
148 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800149 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700150 except KeyError:
151 # ok if extensions don't exist
152 pass
153
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800154 if "fstab_version" not in d:
155 d["fstab_version"] = "1"
156
Tao Bao84e75682015-07-19 02:38:53 -0700157 # A few properties are stored as links to the files in the out/ directory.
158 # It works fine with the build system. However, they are no longer available
159 # when (re)generating from target_files zip. If input_dir is not None, we
160 # are doing repacking. Redirect those properties to the actual files in the
161 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700162 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400163 # We carry a copy of file_contexts.bin under META/. If not available,
164 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700165 # to build images than the one running on device, such as when enabling
166 # system_root_image. In that case, we must have the one for image
167 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700168 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
169 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700170 if d.get("system_root_image") == "true":
171 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700172 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700173 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700174 if not os.path.exists(fc_config):
175 fc_config = None
176
177 if fc_config:
178 d["selinux_fc"] = fc_config
179
Tao Bao84e75682015-07-19 02:38:53 -0700180 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
181 if d.get("system_root_image") == "true":
182 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
183 d["ramdisk_fs_config"] = os.path.join(
184 input_dir, "META", "root_filesystem_config.txt")
185
Doug Zongker37974732010-09-16 17:44:38 -0700186 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800187 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700188 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700189 if not line:
190 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700191 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700192 if not value:
193 continue
Doug Zongker37974732010-09-16 17:44:38 -0700194 if name == "blocksize":
195 d[name] = value
196 else:
197 d[name + "_size"] = value
198 except KeyError:
199 pass
200
201 def makeint(key):
202 if key in d:
203 d[key] = int(d[key], 0)
204
205 makeint("recovery_api_version")
206 makeint("blocksize")
207 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700208 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700209 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700210 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700211 makeint("recovery_size")
212 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800213 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700214
Tao Bao48550cc2015-11-19 17:05:46 -0800215 if d.get("no_recovery", False) == "true":
216 d["fstab"] = None
217 else:
218 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
219 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800220 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700221 return d
222
Doug Zongkerc9253822014-02-04 12:17:58 -0800223def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700224 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800225 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700226 except KeyError:
227 print "Warning: could not find SYSTEM/build.prop in %s" % zip
228 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700229 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700230
Michael Runge6e836112014-04-15 17:40:21 -0700231def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700232 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700233 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700234 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700235 if not line or line.startswith("#"):
236 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700237 if "=" in line:
238 name, value = line.split("=", 1)
239 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700240 return d
241
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700242def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700243 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700244 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700245 self.mount_point = mount_point
246 self.fs_type = fs_type
247 self.device = device
248 self.length = length
249 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700250 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700251
252 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800253 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700254 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800255 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700256 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700257
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800258 if fstab_version == 1:
259 d = {}
260 for line in data.split("\n"):
261 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700262 if not line or line.startswith("#"):
263 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800264 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700265 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800266 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800267 options = None
268 if len(pieces) >= 4:
269 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700270 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800271 if len(pieces) >= 5:
272 options = pieces[4]
273 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700274 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800275 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800276 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700277 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700278
Dan Albert8b72aef2015-03-23 19:13:21 -0700279 mount_point = pieces[0]
280 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800281 if options:
282 options = options.split(",")
283 for i in options:
284 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700285 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800286 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700287 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800288
Dan Albert8b72aef2015-03-23 19:13:21 -0700289 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
290 device=pieces[2], length=length,
291 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800292
293 elif fstab_version == 2:
294 d = {}
295 for line in data.split("\n"):
296 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700297 if not line or line.startswith("#"):
298 continue
Tao Bao548eb762015-06-10 12:32:41 -0700299 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800300 pieces = line.split()
301 if len(pieces) != 5:
302 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
303
304 # Ignore entries that are managed by vold
305 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700306 if "voldmanaged=" in options:
307 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800308
309 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700310 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800311 options = options.split(",")
312 for i in options:
313 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700314 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800315 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800316 # Ignore all unknown options in the unified fstab
317 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800318
Tao Bao548eb762015-06-10 12:32:41 -0700319 mount_flags = pieces[3]
320 # Honor the SELinux context if present.
321 context = None
322 for i in mount_flags.split(","):
323 if i.startswith("context="):
324 context = i
325
Dan Albert8b72aef2015-03-23 19:13:21 -0700326 mount_point = pieces[1]
327 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700328 device=pieces[0], length=length,
329 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800330
331 else:
332 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
333
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700334 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700335 # system. Other areas assume system is always at "/system" so point /system
336 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700337 if system_root_image:
338 assert not d.has_key("/system") and d.has_key("/")
339 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700340 return d
341
342
Doug Zongker37974732010-09-16 17:44:38 -0700343def DumpInfoDict(d):
344 for k, v in sorted(d.items()):
345 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700346
Dan Albert8b72aef2015-03-23 19:13:21 -0700347
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700348def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
349 has_ramdisk=False):
350 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700351
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700352 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
353 'sourcedir'), and turn them into a boot image. Return the image data, or
354 None if sourcedir does not appear to contains files for building the
355 requested image."""
356
357 def make_ramdisk():
358 ramdisk_img = tempfile.NamedTemporaryFile()
359
360 if os.access(fs_config_file, os.F_OK):
361 cmd = ["mkbootfs", "-f", fs_config_file,
362 os.path.join(sourcedir, "RAMDISK")]
363 else:
364 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
365 p1 = Run(cmd, stdout=subprocess.PIPE)
366 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
367
368 p2.wait()
369 p1.wait()
370 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
371 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
372
373 return ramdisk_img
374
375 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
376 return None
377
378 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700379 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700380
Doug Zongkerd5131602012-08-02 14:46:42 -0700381 if info_dict is None:
382 info_dict = OPTIONS.info_dict
383
Doug Zongkereef39442009-04-02 12:14:19 -0700384 img = tempfile.NamedTemporaryFile()
385
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700386 if has_ramdisk:
387 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700388
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800389 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
390 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
391
392 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700393
Benoit Fradina45a8682014-07-14 21:00:43 +0200394 fn = os.path.join(sourcedir, "second")
395 if os.access(fn, os.F_OK):
396 cmd.append("--second")
397 cmd.append(fn)
398
Doug Zongker171f1cd2009-06-15 22:36:37 -0700399 fn = os.path.join(sourcedir, "cmdline")
400 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700401 cmd.append("--cmdline")
402 cmd.append(open(fn).read().rstrip("\n"))
403
404 fn = os.path.join(sourcedir, "base")
405 if os.access(fn, os.F_OK):
406 cmd.append("--base")
407 cmd.append(open(fn).read().rstrip("\n"))
408
Ying Wang4de6b5b2010-08-25 14:29:34 -0700409 fn = os.path.join(sourcedir, "pagesize")
410 if os.access(fn, os.F_OK):
411 cmd.append("--pagesize")
412 cmd.append(open(fn).read().rstrip("\n"))
413
Doug Zongkerd5131602012-08-02 14:46:42 -0700414 args = info_dict.get("mkbootimg_args", None)
415 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700416 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700417
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700418 if has_ramdisk:
419 cmd.extend(["--ramdisk", ramdisk_img.name])
420
Tao Baod95e9fd2015-03-29 23:07:41 -0700421 img_unsigned = None
422 if info_dict.get("vboot", None):
423 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700424 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700425 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700426 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700427
428 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700429 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700430 assert p.returncode == 0, "mkbootimg of %s image failed" % (
431 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700432
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100433 if (info_dict.get("boot_signer", None) == "true" and
434 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700435 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700436 cmd = [OPTIONS.boot_signer_path]
437 cmd.extend(OPTIONS.boot_signer_args)
438 cmd.extend([path, img.name,
439 info_dict["verity_key"] + ".pk8",
440 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700441 p = Run(cmd, stdout=subprocess.PIPE)
442 p.communicate()
443 assert p.returncode == 0, "boot_signer of %s image failed" % path
444
Tao Baod95e9fd2015-03-29 23:07:41 -0700445 # Sign the image if vboot is non-empty.
446 elif info_dict.get("vboot", None):
447 path = "/" + os.path.basename(sourcedir).lower()
448 img_keyblock = tempfile.NamedTemporaryFile()
449 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
450 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700451 info_dict["vboot_key"] + ".vbprivk",
452 info_dict["vboot_subkey"] + ".vbprivk",
453 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700454 img.name]
455 p = Run(cmd, stdout=subprocess.PIPE)
456 p.communicate()
457 assert p.returncode == 0, "vboot_signer of %s image failed" % path
458
Tao Baof3282b42015-04-01 11:21:55 -0700459 # Clean up the temp files.
460 img_unsigned.close()
461 img_keyblock.close()
462
Doug Zongkereef39442009-04-02 12:14:19 -0700463 img.seek(os.SEEK_SET, 0)
464 data = img.read()
465
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700466 if has_ramdisk:
467 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700468 img.close()
469
470 return data
471
472
Doug Zongkerd5131602012-08-02 14:46:42 -0700473def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
474 info_dict=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700475 """Return a File object with the desired bootable image.
476
477 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
478 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
479 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700480
Doug Zongker55d93282011-01-25 17:03:34 -0800481 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
482 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700483 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800484 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700485
486 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
487 if os.path.exists(prebuilt_path):
488 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
489 return File.FromLocalFile(name, prebuilt_path)
490
491 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700492
493 if info_dict is None:
494 info_dict = OPTIONS.info_dict
495
496 # With system_root_image == "true", we don't pack ramdisk into the boot image.
497 has_ramdisk = (info_dict.get("system_root_image", None) != "true" or
498 prebuilt_name != "boot.img")
499
Doug Zongker6f1d0312014-08-22 08:07:12 -0700500 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700501 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
502 os.path.join(unpack_dir, fs_config),
503 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700504 if data:
505 return File(name, data)
506 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800507
Doug Zongkereef39442009-04-02 12:14:19 -0700508
Doug Zongker75f17362009-12-08 13:46:44 -0800509def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800510 """Unzip the given archive into a temporary directory and return the name.
511
512 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
513 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
514
515 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
516 main file), open for reading.
517 """
Doug Zongkereef39442009-04-02 12:14:19 -0700518
519 tmp = tempfile.mkdtemp(prefix="targetfiles-")
520 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800521
522 def unzip_to_dir(filename, dirname):
523 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
524 if pattern is not None:
525 cmd.append(pattern)
526 p = Run(cmd, stdout=subprocess.PIPE)
527 p.communicate()
528 if p.returncode != 0:
529 raise ExternalError("failed to unzip input target-files \"%s\"" %
530 (filename,))
531
532 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
533 if m:
534 unzip_to_dir(m.group(1), tmp)
535 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
536 filename = m.group(1)
537 else:
538 unzip_to_dir(filename, tmp)
539
540 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700541
542
543def GetKeyPasswords(keylist):
544 """Given a list of keys, prompt the user to enter passwords for
545 those which require them. Return a {key: password} dict. password
546 will be None if the key has no password."""
547
Doug Zongker8ce7c252009-05-22 13:34:54 -0700548 no_passwords = []
549 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700550 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700551 devnull = open("/dev/null", "w+b")
552 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800553 # We don't need a password for things that aren't really keys.
554 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700555 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700556 continue
557
T.R. Fullhart37e10522013-03-18 10:31:26 -0700558 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700559 "-inform", "DER", "-nocrypt"],
560 stdin=devnull.fileno(),
561 stdout=devnull.fileno(),
562 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700563 p.communicate()
564 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700565 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700566 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700567 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700568 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
569 "-inform", "DER", "-passin", "pass:"],
570 stdin=devnull.fileno(),
571 stdout=devnull.fileno(),
572 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700573 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700574 if p.returncode == 0:
575 # Encrypted key with empty string as password.
576 key_passwords[k] = ''
577 elif stderr.startswith('Error decrypting key'):
578 # Definitely encrypted key.
579 # It would have said "Error reading key" if it didn't parse correctly.
580 need_passwords.append(k)
581 else:
582 # Potentially, a type of key that openssl doesn't understand.
583 # We'll let the routines in signapk.jar handle it.
584 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700585 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700586
T.R. Fullhart37e10522013-03-18 10:31:26 -0700587 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700588 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700589 return key_passwords
590
591
Alex Klyubineb756d72015-12-04 09:21:08 -0800592def SignFile(input_name, output_name, key, password, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700593 """Sign the input_name zip/jar/apk, producing output_name. Use the
594 given key and password (the latter may be None if the key does not
595 have a password.
596
Doug Zongker951495f2009-08-14 12:44:19 -0700597 If whole_file is true, use the "-w" option to SignApk to embed a
598 signature that covers the whole file in the archive comment of the
599 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700600 """
Doug Zongker951495f2009-08-14 12:44:19 -0700601
Alex Klyubin9667b182015-12-10 13:38:50 -0800602 java_library_path = os.path.join(
603 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
604
605 cmd = [OPTIONS.java_path, OPTIONS.java_args,
606 "-Djava.library.path=" + java_library_path,
607 "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700608 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
609 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700610 if whole_file:
611 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700612 cmd.extend([key + OPTIONS.public_key_suffix,
613 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800614 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700615
616 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700617 if password is not None:
618 password += "\n"
619 p.communicate(password)
620 if p.returncode != 0:
621 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
622
Doug Zongkereef39442009-04-02 12:14:19 -0700623
Doug Zongker37974732010-09-16 17:44:38 -0700624def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700625 """Check the data string passed against the max size limit, if
626 any, for the given target. Raise exception if the data is too big.
627 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700628
Dan Albert8b72aef2015-03-23 19:13:21 -0700629 if target.endswith(".img"):
630 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700631 mount_point = "/" + target
632
Ying Wangf8824af2014-06-03 14:07:27 -0700633 fs_type = None
634 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700635 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700636 if mount_point == "/userdata":
637 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700638 p = info_dict["fstab"][mount_point]
639 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800640 device = p.device
641 if "/" in device:
642 device = device[device.rfind("/")+1:]
643 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700644 if not fs_type or not limit:
645 return
Doug Zongkereef39442009-04-02 12:14:19 -0700646
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700647 if fs_type == "yaffs2":
648 # image size should be increased by 1/64th to account for the
649 # spare area (64 bytes per 2k page)
650 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800651 size = len(data)
652 pct = float(size) * 100.0 / limit
653 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
654 if pct >= 99.0:
655 raise ExternalError(msg)
656 elif pct >= 95.0:
657 print
658 print " WARNING: ", msg
659 print
660 elif OPTIONS.verbose:
661 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700662
663
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800664def ReadApkCerts(tf_zip):
665 """Given a target_files ZipFile, parse the META/apkcerts.txt file
666 and return a {package: cert} dict."""
667 certmap = {}
668 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
669 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700670 if not line:
671 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800672 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
673 r'private_key="(.*)"$', line)
674 if m:
675 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700676 public_key_suffix_len = len(OPTIONS.public_key_suffix)
677 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800678 if cert in SPECIAL_CERT_STRINGS and not privkey:
679 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700680 elif (cert.endswith(OPTIONS.public_key_suffix) and
681 privkey.endswith(OPTIONS.private_key_suffix) and
682 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
683 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800684 else:
685 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
686 return certmap
687
688
Doug Zongkereef39442009-04-02 12:14:19 -0700689COMMON_DOCSTRING = """
690 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700691 Prepend <dir>/bin to the list of places to search for binaries
692 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700693
Doug Zongker05d3dea2009-06-22 11:32:31 -0700694 -s (--device_specific) <file>
695 Path to the python module containing device-specific
696 releasetools code.
697
Doug Zongker8bec09e2009-11-30 15:37:14 -0800698 -x (--extra) <key=value>
699 Add a key/value pair to the 'extras' dict, which device-specific
700 extension code may look at.
701
Doug Zongkereef39442009-04-02 12:14:19 -0700702 -v (--verbose)
703 Show command lines being executed.
704
705 -h (--help)
706 Display this usage message and exit.
707"""
708
709def Usage(docstring):
710 print docstring.rstrip("\n")
711 print COMMON_DOCSTRING
712
713
714def ParseOptions(argv,
715 docstring,
716 extra_opts="", extra_long_opts=(),
717 extra_option_handler=None):
718 """Parse the options in argv and return any arguments that aren't
719 flags. docstring is the calling module's docstring, to be displayed
720 for errors and -h. extra_opts and extra_long_opts are for flags
721 defined by the caller, which are processed by passing them to
722 extra_option_handler."""
723
724 try:
725 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800726 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800727 ["help", "verbose", "path=", "signapk_path=",
728 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700729 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700730 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
731 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800732 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700733 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700734 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700735 Usage(docstring)
736 print "**", str(err), "**"
737 sys.exit(2)
738
Doug Zongkereef39442009-04-02 12:14:19 -0700739 for o, a in opts:
740 if o in ("-h", "--help"):
741 Usage(docstring)
742 sys.exit()
743 elif o in ("-v", "--verbose"):
744 OPTIONS.verbose = True
745 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700746 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700747 elif o in ("--signapk_path",):
748 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800749 elif o in ("--signapk_shared_library_path",):
750 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700751 elif o in ("--extra_signapk_args",):
752 OPTIONS.extra_signapk_args = shlex.split(a)
753 elif o in ("--java_path",):
754 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700755 elif o in ("--java_args",):
756 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700757 elif o in ("--public_key_suffix",):
758 OPTIONS.public_key_suffix = a
759 elif o in ("--private_key_suffix",):
760 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800761 elif o in ("--boot_signer_path",):
762 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700763 elif o in ("--boot_signer_args",):
764 OPTIONS.boot_signer_args = shlex.split(a)
765 elif o in ("--verity_signer_path",):
766 OPTIONS.verity_signer_path = a
767 elif o in ("--verity_signer_args",):
768 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700769 elif o in ("-s", "--device_specific"):
770 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800771 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800772 key, value = a.split("=", 1)
773 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700774 else:
775 if extra_option_handler is None or not extra_option_handler(o, a):
776 assert False, "unknown option \"%s\"" % (o,)
777
Doug Zongker85448772014-09-09 14:59:20 -0700778 if OPTIONS.search_path:
779 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
780 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700781
782 return args
783
784
Doug Zongkerfc44a512014-08-26 13:10:25 -0700785def MakeTempFile(prefix=None, suffix=None):
786 """Make a temp file and add it to the list of things to be deleted
787 when Cleanup() is called. Return the filename."""
788 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
789 os.close(fd)
790 OPTIONS.tempfiles.append(fn)
791 return fn
792
793
Doug Zongkereef39442009-04-02 12:14:19 -0700794def Cleanup():
795 for i in OPTIONS.tempfiles:
796 if os.path.isdir(i):
797 shutil.rmtree(i)
798 else:
799 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700800
801
802class PasswordManager(object):
803 def __init__(self):
804 self.editor = os.getenv("EDITOR", None)
805 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
806
807 def GetPasswords(self, items):
808 """Get passwords corresponding to each string in 'items',
809 returning a dict. (The dict may have keys in addition to the
810 values in 'items'.)
811
812 Uses the passwords in $ANDROID_PW_FILE if available, letting the
813 user edit that file to add more needed passwords. If no editor is
814 available, or $ANDROID_PW_FILE isn't define, prompts the user
815 interactively in the ordinary way.
816 """
817
818 current = self.ReadFile()
819
820 first = True
821 while True:
822 missing = []
823 for i in items:
824 if i not in current or not current[i]:
825 missing.append(i)
826 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700827 if not missing:
828 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700829
830 for i in missing:
831 current[i] = ""
832
833 if not first:
834 print "key file %s still missing some passwords." % (self.pwfile,)
835 answer = raw_input("try to edit again? [y]> ").strip()
836 if answer and answer[0] not in 'yY':
837 raise RuntimeError("key passwords unavailable")
838 first = False
839
840 current = self.UpdateAndReadFile(current)
841
Dan Albert8b72aef2015-03-23 19:13:21 -0700842 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700843 """Prompt the user to enter a value (password) for each key in
844 'current' whose value is fales. Returns a new dict with all the
845 values.
846 """
847 result = {}
848 for k, v in sorted(current.iteritems()):
849 if v:
850 result[k] = v
851 else:
852 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700853 result[k] = getpass.getpass(
854 "Enter password for %s key> " % k).strip()
855 if result[k]:
856 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700857 return result
858
859 def UpdateAndReadFile(self, current):
860 if not self.editor or not self.pwfile:
861 return self.PromptResult(current)
862
863 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700864 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700865 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
866 f.write("# (Additional spaces are harmless.)\n\n")
867
868 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700869 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
870 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700871 f.write("[[[ %s ]]] %s\n" % (v, k))
872 if not v and first_line is None:
873 # position cursor on first line with no password.
874 first_line = i + 4
875 f.close()
876
877 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
878 _, _ = p.communicate()
879
880 return self.ReadFile()
881
882 def ReadFile(self):
883 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700884 if self.pwfile is None:
885 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700886 try:
887 f = open(self.pwfile, "r")
888 for line in f:
889 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700890 if not line or line[0] == '#':
891 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700892 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
893 if not m:
894 print "failed to parse password file: ", line
895 else:
896 result[m.group(2)] = m.group(1)
897 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700898 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700899 if e.errno != errno.ENOENT:
900 print "error reading password file: ", str(e)
901 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700902
903
Dan Albert8e0178d2015-01-27 15:53:15 -0800904def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
905 compress_type=None):
906 import datetime
907
908 # http://b/18015246
909 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
910 # for files larger than 2GiB. We can work around this by adjusting their
911 # limit. Note that `zipfile.writestr()` will not work for strings larger than
912 # 2GiB. The Python interpreter sometimes rejects strings that large (though
913 # it isn't clear to me exactly what circumstances cause this).
914 # `zipfile.write()` must be used directly to work around this.
915 #
916 # This mess can be avoided if we port to python3.
917 saved_zip64_limit = zipfile.ZIP64_LIMIT
918 zipfile.ZIP64_LIMIT = (1 << 32) - 1
919
920 if compress_type is None:
921 compress_type = zip_file.compression
922 if arcname is None:
923 arcname = filename
924
925 saved_stat = os.stat(filename)
926
927 try:
928 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
929 # file to be zipped and reset it when we're done.
930 os.chmod(filename, perms)
931
932 # Use a fixed timestamp so the output is repeatable.
933 epoch = datetime.datetime.fromtimestamp(0)
934 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
935 os.utime(filename, (timestamp, timestamp))
936
937 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
938 finally:
939 os.chmod(filename, saved_stat.st_mode)
940 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
941 zipfile.ZIP64_LIMIT = saved_zip64_limit
942
943
Tao Bao58c1b962015-05-20 09:32:18 -0700944def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700945 compress_type=None):
946 """Wrap zipfile.writestr() function to work around the zip64 limit.
947
948 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
949 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
950 when calling crc32(bytes).
951
952 But it still works fine to write a shorter string into a large zip file.
953 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
954 when we know the string won't be too long.
955 """
956
957 saved_zip64_limit = zipfile.ZIP64_LIMIT
958 zipfile.ZIP64_LIMIT = (1 << 32) - 1
959
960 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
961 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700962 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700963 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700964 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800965 else:
Tao Baof3282b42015-04-01 11:21:55 -0700966 zinfo = zinfo_or_arcname
967
968 # If compress_type is given, it overrides the value in zinfo.
969 if compress_type is not None:
970 zinfo.compress_type = compress_type
971
Tao Bao58c1b962015-05-20 09:32:18 -0700972 # If perms is given, it has a priority.
973 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700974 # If perms doesn't set the file type, mark it as a regular file.
975 if perms & 0o770000 == 0:
976 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -0700977 zinfo.external_attr = perms << 16
978
Tao Baof3282b42015-04-01 11:21:55 -0700979 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700980 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
981
Dan Albert8b72aef2015-03-23 19:13:21 -0700982 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700983 zipfile.ZIP64_LIMIT = saved_zip64_limit
984
985
986def ZipClose(zip_file):
987 # http://b/18015246
988 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
989 # central directory.
990 saved_zip64_limit = zipfile.ZIP64_LIMIT
991 zipfile.ZIP64_LIMIT = (1 << 32) - 1
992
993 zip_file.close()
994
995 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700996
997
998class DeviceSpecificParams(object):
999 module = None
1000 def __init__(self, **kwargs):
1001 """Keyword arguments to the constructor become attributes of this
1002 object, which is passed to all functions in the device-specific
1003 module."""
1004 for k, v in kwargs.iteritems():
1005 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001006 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001007
1008 if self.module is None:
1009 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001010 if not path:
1011 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001012 try:
1013 if os.path.isdir(path):
1014 info = imp.find_module("releasetools", [path])
1015 else:
1016 d, f = os.path.split(path)
1017 b, x = os.path.splitext(f)
1018 if x == ".py":
1019 f = b
1020 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001021 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001022 self.module = imp.load_module("device_specific", *info)
1023 except ImportError:
1024 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001025
1026 def _DoCall(self, function_name, *args, **kwargs):
1027 """Call the named function in the device-specific module, passing
1028 the given args and kwargs. The first argument to the call will be
1029 the DeviceSpecific object itself. If there is no module, or the
1030 module does not define the function, return the value of the
1031 'default' kwarg (which itself defaults to None)."""
1032 if self.module is None or not hasattr(self.module, function_name):
1033 return kwargs.get("default", None)
1034 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1035
1036 def FullOTA_Assertions(self):
1037 """Called after emitting the block of assertions at the top of a
1038 full OTA package. Implementations can add whatever additional
1039 assertions they like."""
1040 return self._DoCall("FullOTA_Assertions")
1041
Doug Zongkere5ff5902012-01-17 10:55:37 -08001042 def FullOTA_InstallBegin(self):
1043 """Called at the start of full OTA installation."""
1044 return self._DoCall("FullOTA_InstallBegin")
1045
Doug Zongker05d3dea2009-06-22 11:32:31 -07001046 def FullOTA_InstallEnd(self):
1047 """Called at the end of full OTA installation; typically this is
1048 used to install the image for the device's baseband processor."""
1049 return self._DoCall("FullOTA_InstallEnd")
1050
1051 def IncrementalOTA_Assertions(self):
1052 """Called after emitting the block of assertions at the top of an
1053 incremental OTA package. Implementations can add whatever
1054 additional assertions they like."""
1055 return self._DoCall("IncrementalOTA_Assertions")
1056
Doug Zongkere5ff5902012-01-17 10:55:37 -08001057 def IncrementalOTA_VerifyBegin(self):
1058 """Called at the start of the verification phase of incremental
1059 OTA installation; additional checks can be placed here to abort
1060 the script before any changes are made."""
1061 return self._DoCall("IncrementalOTA_VerifyBegin")
1062
Doug Zongker05d3dea2009-06-22 11:32:31 -07001063 def IncrementalOTA_VerifyEnd(self):
1064 """Called at the end of the verification phase of incremental OTA
1065 installation; additional checks can be placed here to abort the
1066 script before any changes are made."""
1067 return self._DoCall("IncrementalOTA_VerifyEnd")
1068
Doug Zongkere5ff5902012-01-17 10:55:37 -08001069 def IncrementalOTA_InstallBegin(self):
1070 """Called at the start of incremental OTA installation (after
1071 verification is complete)."""
1072 return self._DoCall("IncrementalOTA_InstallBegin")
1073
Doug Zongker05d3dea2009-06-22 11:32:31 -07001074 def IncrementalOTA_InstallEnd(self):
1075 """Called at the end of incremental OTA installation; typically
1076 this is used to install the image for the device's baseband
1077 processor."""
1078 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001079
Tao Bao9bc6bb22015-11-09 16:58:28 -08001080 def VerifyOTA_Assertions(self):
1081 return self._DoCall("VerifyOTA_Assertions")
1082
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001083class File(object):
1084 def __init__(self, name, data):
1085 self.name = name
1086 self.data = data
1087 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001088 self.sha1 = sha1(data).hexdigest()
1089
1090 @classmethod
1091 def FromLocalFile(cls, name, diskname):
1092 f = open(diskname, "rb")
1093 data = f.read()
1094 f.close()
1095 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001096
1097 def WriteToTemp(self):
1098 t = tempfile.NamedTemporaryFile()
1099 t.write(self.data)
1100 t.flush()
1101 return t
1102
Geremy Condra36bd3652014-02-06 19:45:10 -08001103 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001104 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001105
1106DIFF_PROGRAM_BY_EXT = {
1107 ".gz" : "imgdiff",
1108 ".zip" : ["imgdiff", "-z"],
1109 ".jar" : ["imgdiff", "-z"],
1110 ".apk" : ["imgdiff", "-z"],
1111 ".img" : "imgdiff",
1112 }
1113
1114class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001115 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001116 self.tf = tf
1117 self.sf = sf
1118 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001119 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001120
1121 def ComputePatch(self):
1122 """Compute the patch (as a string of data) needed to turn sf into
1123 tf. Returns the same tuple as GetPatch()."""
1124
1125 tf = self.tf
1126 sf = self.sf
1127
Doug Zongker24cd2802012-08-14 16:36:15 -07001128 if self.diff_program:
1129 diff_program = self.diff_program
1130 else:
1131 ext = os.path.splitext(tf.name)[1]
1132 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001133
1134 ttemp = tf.WriteToTemp()
1135 stemp = sf.WriteToTemp()
1136
1137 ext = os.path.splitext(tf.name)[1]
1138
1139 try:
1140 ptemp = tempfile.NamedTemporaryFile()
1141 if isinstance(diff_program, list):
1142 cmd = copy.copy(diff_program)
1143 else:
1144 cmd = [diff_program]
1145 cmd.append(stemp.name)
1146 cmd.append(ttemp.name)
1147 cmd.append(ptemp.name)
1148 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001149 err = []
1150 def run():
1151 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001152 if e:
1153 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001154 th = threading.Thread(target=run)
1155 th.start()
1156 th.join(timeout=300) # 5 mins
1157 if th.is_alive():
1158 print "WARNING: diff command timed out"
1159 p.terminate()
1160 th.join(5)
1161 if th.is_alive():
1162 p.kill()
1163 th.join()
1164
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001165 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001166 print "WARNING: failure running %s:\n%s\n" % (
1167 diff_program, "".join(err))
1168 self.patch = None
1169 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001170 diff = ptemp.read()
1171 finally:
1172 ptemp.close()
1173 stemp.close()
1174 ttemp.close()
1175
1176 self.patch = diff
1177 return self.tf, self.sf, self.patch
1178
1179
1180 def GetPatch(self):
1181 """Return a tuple (target_file, source_file, patch_data).
1182 patch_data may be None if ComputePatch hasn't been called, or if
1183 computing the patch failed."""
1184 return self.tf, self.sf, self.patch
1185
1186
1187def ComputeDifferences(diffs):
1188 """Call ComputePatch on all the Difference objects in 'diffs'."""
1189 print len(diffs), "diffs to compute"
1190
1191 # Do the largest files first, to try and reduce the long-pole effect.
1192 by_size = [(i.tf.size, i) for i in diffs]
1193 by_size.sort(reverse=True)
1194 by_size = [i[1] for i in by_size]
1195
1196 lock = threading.Lock()
1197 diff_iter = iter(by_size) # accessed under lock
1198
1199 def worker():
1200 try:
1201 lock.acquire()
1202 for d in diff_iter:
1203 lock.release()
1204 start = time.time()
1205 d.ComputePatch()
1206 dur = time.time() - start
1207 lock.acquire()
1208
1209 tf, sf, patch = d.GetPatch()
1210 if sf.name == tf.name:
1211 name = tf.name
1212 else:
1213 name = "%s (%s)" % (tf.name, sf.name)
1214 if patch is None:
1215 print "patching failed! %s" % (name,)
1216 else:
1217 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1218 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1219 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001220 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001221 print e
1222 raise
1223
1224 # start worker threads; wait for them all to finish.
1225 threads = [threading.Thread(target=worker)
1226 for i in range(OPTIONS.worker_threads)]
1227 for th in threads:
1228 th.start()
1229 while threads:
1230 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001231
1232
Dan Albert8b72aef2015-03-23 19:13:21 -07001233class BlockDifference(object):
1234 def __init__(self, partition, tgt, src=None, check_first_block=False,
1235 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001236 self.tgt = tgt
1237 self.src = src
1238 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001239 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001240
Tao Bao5ece99d2015-05-12 11:42:31 -07001241 # Due to http://b/20939131, check_first_block is disabled temporarily.
1242 assert not self.check_first_block
1243
Tao Baodd2a5892015-03-12 12:32:37 -07001244 if version is None:
1245 version = 1
1246 if OPTIONS.info_dict:
1247 version = max(
1248 int(i) for i in
1249 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1250 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001251
1252 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001253 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001254 tmpdir = tempfile.mkdtemp()
1255 OPTIONS.tempfiles.append(tmpdir)
1256 self.path = os.path.join(tmpdir, partition)
1257 b.Compute(self.path)
1258
Tao Baoaac4ad52015-10-16 15:26:34 -07001259 if src is None:
1260 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1261 else:
1262 _, self.device = GetTypeAndDevice("/" + partition,
1263 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001264
1265 def WriteScript(self, script, output_zip, progress=None):
1266 if not self.src:
1267 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001268 script.Print("Patching %s image unconditionally..." % (self.partition,))
1269 else:
1270 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001271
Dan Albert8b72aef2015-03-23 19:13:21 -07001272 if progress:
1273 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001274 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001275 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001276
Tao Bao9bc6bb22015-11-09 16:58:28 -08001277 def WriteStrictVerifyScript(self, script):
1278 """Verify all the blocks in the care_map, including clobbered blocks.
1279
1280 This differs from the WriteVerifyScript() function: a) it prints different
1281 error messages; b) it doesn't allow half-way updated images to pass the
1282 verification."""
1283
1284 partition = self.partition
1285 script.Print("Verifying %s..." % (partition,))
1286 ranges = self.tgt.care_map
1287 ranges_str = ranges.to_string_raw()
1288 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1289 'ui_print(" Verified.") || '
1290 'ui_print("\\"%s\\" has unexpected contents.");' % (
1291 self.device, ranges_str,
1292 self.tgt.TotalSha1(include_clobbered_blocks=True),
1293 self.device))
1294 script.AppendExtra("")
1295
Jesse Zhao75bcea02015-01-06 10:59:53 -08001296 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001297 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001298 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001299 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001300 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001301 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1302 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001303 if self.version >= 4:
1304 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1305 'block_image_verify("%s", '
1306 'package_extract_file("%s.transfer.list"), '
1307 '"%s.new.dat", "%s.patch.dat") || '
1308 '(block_image_recover("%s", "%s") && '
1309 'block_image_verify("%s", '
1310 'package_extract_file("%s.transfer.list"), '
1311 '"%s.new.dat", "%s.patch.dat"))) then') % (
1312 self.device, ranges_str, self.src.TotalSha1(),
1313 self.device, partition, partition, partition,
1314 self.device, ranges_str,
1315 self.device, partition, partition, partition))
1316 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001317 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1318 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001319 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001320 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001321 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001322 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001323 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001324 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001325 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001326 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001327 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001328
Tao Baodd2a5892015-03-12 12:32:37 -07001329 # When generating incrementals for the system and vendor partitions,
1330 # explicitly check the first block (which contains the superblock) of
1331 # the partition to see if it's what we expect. If this check fails,
1332 # give an explicit log message about the partition having been
1333 # remounted R/W (the most likely explanation) and the need to flash to
1334 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001335 if self.check_first_block:
1336 self._CheckFirstBlock(script)
1337
Tao Baodd2a5892015-03-12 12:32:37 -07001338 # Abort the OTA update. Note that the incremental OTA cannot be applied
1339 # even if it may match the checksum of the target partition.
1340 # a) If version < 3, operations like move and erase will make changes
1341 # unconditionally and damage the partition.
1342 # b) If version >= 3, it won't even reach here.
1343 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1344 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001345
Tao Bao5fcaaef2015-06-01 13:40:49 -07001346 def _WritePostInstallVerifyScript(self, script):
1347 partition = self.partition
1348 script.Print('Verifying the updated %s image...' % (partition,))
1349 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1350 ranges = self.tgt.care_map
1351 ranges_str = ranges.to_string_raw()
1352 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1353 self.device, ranges_str,
1354 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001355
1356 # Bug: 20881595
1357 # Verify that extended blocks are really zeroed out.
1358 if self.tgt.extended:
1359 ranges_str = self.tgt.extended.to_string_raw()
1360 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1361 self.device, ranges_str,
1362 self._HashZeroBlocks(self.tgt.extended.size())))
1363 script.Print('Verified the updated %s image.' % (partition,))
1364 script.AppendExtra(
1365 'else\n'
1366 ' abort("%s partition has unexpected non-zero contents after OTA '
1367 'update");\n'
1368 'endif;' % (partition,))
1369 else:
1370 script.Print('Verified the updated %s image.' % (partition,))
1371
Tao Bao5fcaaef2015-06-01 13:40:49 -07001372 script.AppendExtra(
1373 'else\n'
1374 ' abort("%s partition has unexpected contents after OTA update");\n'
1375 'endif;' % (partition,))
1376
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001377 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001378 ZipWrite(output_zip,
1379 '{}.transfer.list'.format(self.path),
1380 '{}.transfer.list'.format(self.partition))
1381 ZipWrite(output_zip,
1382 '{}.new.dat'.format(self.path),
1383 '{}.new.dat'.format(self.partition))
1384 ZipWrite(output_zip,
1385 '{}.patch.dat'.format(self.path),
1386 '{}.patch.dat'.format(self.partition),
1387 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001388
Dan Albert8e0178d2015-01-27 15:53:15 -08001389 call = ('block_image_update("{device}", '
1390 'package_extract_file("{partition}.transfer.list"), '
1391 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1392 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001393 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001394
Dan Albert8b72aef2015-03-23 19:13:21 -07001395 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001396 data = source.ReadRangeSet(ranges)
1397 ctx = sha1()
1398
1399 for p in data:
1400 ctx.update(p)
1401
1402 return ctx.hexdigest()
1403
Tao Baoe9b61912015-07-09 17:37:49 -07001404 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1405 """Return the hash value for all zero blocks."""
1406 zero_block = '\x00' * 4096
1407 ctx = sha1()
1408 for _ in range(num_blocks):
1409 ctx.update(zero_block)
1410
1411 return ctx.hexdigest()
1412
Tao Bao5ece99d2015-05-12 11:42:31 -07001413 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1414 # remounting R/W. Will change the checking to a finer-grained way to
1415 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001416 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001417 r = rangelib.RangeSet((0, 1))
1418 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001419
1420 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1421 'abort("%s has been remounted R/W; '
1422 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001423 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001424 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001425
1426DataImage = blockimgdiff.DataImage
1427
1428
Doug Zongker96a57e72010-09-26 14:57:41 -07001429# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001430PARTITION_TYPES = {
1431 "yaffs2": "MTD",
1432 "mtd": "MTD",
1433 "ext4": "EMMC",
1434 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001435 "f2fs": "EMMC",
1436 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001437}
Doug Zongker96a57e72010-09-26 14:57:41 -07001438
1439def GetTypeAndDevice(mount_point, info):
1440 fstab = info["fstab"]
1441 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001442 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1443 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001444 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001445 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001446
1447
1448def ParseCertificate(data):
1449 """Parse a PEM-format certificate."""
1450 cert = []
1451 save = False
1452 for line in data.split("\n"):
1453 if "--END CERTIFICATE--" in line:
1454 break
1455 if save:
1456 cert.append(line)
1457 if "--BEGIN CERTIFICATE--" in line:
1458 save = True
1459 cert = "".join(cert).decode('base64')
1460 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001461
Doug Zongker412c02f2014-02-13 10:58:24 -08001462def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1463 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001464 """Generate a binary patch that creates the recovery image starting
1465 with the boot image. (Most of the space in these images is just the
1466 kernel, which is identical for the two, so the resulting patch
1467 should be efficient.) Add it to the output zip, along with a shell
1468 script that is run from init.rc on first boot to actually do the
1469 patching and install the new recovery image.
1470
1471 recovery_img and boot_img should be File objects for the
1472 corresponding images. info should be the dictionary returned by
1473 common.LoadInfoDict() on the input target_files.
1474 """
1475
Doug Zongker412c02f2014-02-13 10:58:24 -08001476 if info_dict is None:
1477 info_dict = OPTIONS.info_dict
1478
Tao Baof2cffbd2015-07-22 12:33:18 -07001479 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001480 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001481
Tao Baof2cffbd2015-07-22 12:33:18 -07001482 if full_recovery_image:
1483 output_sink("etc/recovery.img", recovery_img.data)
1484
1485 else:
1486 diff_program = ["imgdiff"]
1487 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1488 if os.path.exists(path):
1489 diff_program.append("-b")
1490 diff_program.append(path)
1491 bonus_args = "-b /system/etc/recovery-resource.dat"
1492 else:
1493 bonus_args = ""
1494
1495 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1496 _, _, patch = d.ComputePatch()
1497 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001498
Dan Albertebb19aa2015-03-27 19:11:53 -07001499 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001500 # The following GetTypeAndDevice()s need to use the path in the target
1501 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001502 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1503 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1504 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001505 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001506
Tao Baof2cffbd2015-07-22 12:33:18 -07001507 if full_recovery_image:
1508 sh = """#!/system/bin/sh
1509if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1510 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"
1511else
1512 log -t recovery "Recovery image already installed"
1513fi
1514""" % {'type': recovery_type,
1515 'device': recovery_device,
1516 'sha1': recovery_img.sha1,
1517 'size': recovery_img.size}
1518 else:
1519 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001520if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1521 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"
1522else
1523 log -t recovery "Recovery image already installed"
1524fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001525""" % {'boot_size': boot_img.size,
1526 'boot_sha1': boot_img.sha1,
1527 'recovery_size': recovery_img.size,
1528 'recovery_sha1': recovery_img.sha1,
1529 'boot_type': boot_type,
1530 'boot_device': boot_device,
1531 'recovery_type': recovery_type,
1532 'recovery_device': recovery_device,
1533 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001534
1535 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001536 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001537 # target-files expects it to be, and put it there.
1538 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001539 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001540 if system_root_image:
1541 init_rc_dir = os.path.join(input_dir, "ROOT")
1542 else:
1543 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001544 init_rc_files = os.listdir(init_rc_dir)
1545 for init_rc_file in init_rc_files:
1546 if (not init_rc_file.startswith('init.') or
1547 not init_rc_file.endswith('.rc')):
1548 continue
1549
1550 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001551 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001552 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001553 if m:
1554 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001555 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001556 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001557
1558 if found:
1559 break
1560
1561 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001562
1563 output_sink(sh_location, sh)