blob: 3b2ce7292d36d7f6d901d4fedb3c17c3aa18e00b [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
47 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
49 self.java_args = "-Xmx2048m" # JVM Args
50 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
76
Dan Albert8b72aef2015-03-23 19:13:21 -070077class ExternalError(RuntimeError):
78 pass
Doug Zongkereef39442009-04-02 12:14:19 -070079
80
81def Run(args, **kwargs):
82 """Create and return a subprocess.Popen object, printing the command
83 line on the terminal if -v was specified."""
84 if OPTIONS.verbose:
85 print " running: ", " ".join(args)
86 return subprocess.Popen(args, **kwargs)
87
88
Ying Wang7e6d4e42010-12-13 16:25:36 -080089def CloseInheritedPipes():
90 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
91 before doing other work."""
92 if platform.system() != "Darwin":
93 return
94 for d in range(3, 1025):
95 try:
96 stat = os.fstat(d)
97 if stat is not None:
98 pipebit = stat[0] & 0x1000
99 if pipebit != 0:
100 os.close(d)
101 except OSError:
102 pass
103
104
Tao Bao2c15d9e2015-07-09 11:51:16 -0700105def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700106 """Read and parse the META/misc_info.txt key/value pairs from the
107 input target files and return a dict."""
108
Doug Zongkerc9253822014-02-04 12:17:58 -0800109 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700110 if isinstance(input_file, zipfile.ZipFile):
111 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800112 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700113 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800114 try:
115 with open(path) as f:
116 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700117 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800118 if e.errno == errno.ENOENT:
119 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700120 d = {}
121 try:
Michael Runge6e836112014-04-15 17:40:21 -0700122 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700123 except KeyError:
124 # ok if misc_info.txt doesn't exist
125 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700126
Doug Zongker37974732010-09-16 17:44:38 -0700127 # backwards compatibility: These values used to be in their own
128 # files. Look for them, in case we're processing an old
129 # target_files zip.
130
131 if "mkyaffs2_extra_flags" not in d:
132 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700133 d["mkyaffs2_extra_flags"] = read_helper(
134 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700135 except KeyError:
136 # ok if flags don't exist
137 pass
138
139 if "recovery_api_version" not in d:
140 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700141 d["recovery_api_version"] = read_helper(
142 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700143 except KeyError:
144 raise ValueError("can't find recovery API version in input target-files")
145
146 if "tool_extensions" not in d:
147 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800148 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700149 except KeyError:
150 # ok if extensions don't exist
151 pass
152
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800153 if "fstab_version" not in d:
154 d["fstab_version"] = "1"
155
Tao Bao84e75682015-07-19 02:38:53 -0700156 # A few properties are stored as links to the files in the out/ directory.
157 # It works fine with the build system. However, they are no longer available
158 # when (re)generating from target_files zip. If input_dir is not None, we
159 # are doing repacking. Redirect those properties to the actual files in the
160 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700161 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400162 # We carry a copy of file_contexts.bin under META/. If not available,
163 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700164 # to build images than the one running on device, such as when enabling
165 # system_root_image. In that case, we must have the one for image
166 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700167 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
168 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700169 if d.get("system_root_image") == "true":
170 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700171 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700172 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700173 if not os.path.exists(fc_config):
174 fc_config = None
175
176 if fc_config:
177 d["selinux_fc"] = fc_config
178
Tao Bao84e75682015-07-19 02:38:53 -0700179 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
180 if d.get("system_root_image") == "true":
181 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
182 d["ramdisk_fs_config"] = os.path.join(
183 input_dir, "META", "root_filesystem_config.txt")
184
Doug Zongker37974732010-09-16 17:44:38 -0700185 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800186 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700187 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700188 if not line:
189 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700190 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700191 if not value:
192 continue
Doug Zongker37974732010-09-16 17:44:38 -0700193 if name == "blocksize":
194 d[name] = value
195 else:
196 d[name + "_size"] = value
197 except KeyError:
198 pass
199
200 def makeint(key):
201 if key in d:
202 d[key] = int(d[key], 0)
203
204 makeint("recovery_api_version")
205 makeint("blocksize")
206 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700207 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700208 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700209 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700210 makeint("recovery_size")
211 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800212 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700213
Tao Bao48550cc2015-11-19 17:05:46 -0800214 if d.get("no_recovery", False) == "true":
215 d["fstab"] = None
216 else:
217 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
218 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800219 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700220 return d
221
Doug Zongkerc9253822014-02-04 12:17:58 -0800222def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700223 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800224 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700225 except KeyError:
226 print "Warning: could not find SYSTEM/build.prop in %s" % zip
227 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700228 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700229
Michael Runge6e836112014-04-15 17:40:21 -0700230def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700231 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700232 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700233 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700234 if not line or line.startswith("#"):
235 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700236 if "=" in line:
237 name, value = line.split("=", 1)
238 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700239 return d
240
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700241def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700242 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700243 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700244 self.mount_point = mount_point
245 self.fs_type = fs_type
246 self.device = device
247 self.length = length
248 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700249 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700250
251 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800252 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700253 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800254 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700255 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700256
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800257 if fstab_version == 1:
258 d = {}
259 for line in data.split("\n"):
260 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700261 if not line or line.startswith("#"):
262 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800263 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700264 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800265 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800266 options = None
267 if len(pieces) >= 4:
268 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700269 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800270 if len(pieces) >= 5:
271 options = pieces[4]
272 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700273 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800274 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800275 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700276 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700277
Dan Albert8b72aef2015-03-23 19:13:21 -0700278 mount_point = pieces[0]
279 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800280 if options:
281 options = options.split(",")
282 for i in options:
283 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700284 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800285 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700286 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800287
Dan Albert8b72aef2015-03-23 19:13:21 -0700288 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
289 device=pieces[2], length=length,
290 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800291
292 elif fstab_version == 2:
293 d = {}
294 for line in data.split("\n"):
295 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700296 if not line or line.startswith("#"):
297 continue
Tao Bao548eb762015-06-10 12:32:41 -0700298 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800299 pieces = line.split()
300 if len(pieces) != 5:
301 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
302
303 # Ignore entries that are managed by vold
304 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700305 if "voldmanaged=" in options:
306 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800307
308 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700309 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800310 options = options.split(",")
311 for i in options:
312 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700313 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800314 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800315 # Ignore all unknown options in the unified fstab
316 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800317
Tao Bao548eb762015-06-10 12:32:41 -0700318 mount_flags = pieces[3]
319 # Honor the SELinux context if present.
320 context = None
321 for i in mount_flags.split(","):
322 if i.startswith("context="):
323 context = i
324
Dan Albert8b72aef2015-03-23 19:13:21 -0700325 mount_point = pieces[1]
326 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700327 device=pieces[0], length=length,
328 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800329
330 else:
331 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
332
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700333 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700334 # system. Other areas assume system is always at "/system" so point /system
335 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700336 if system_root_image:
337 assert not d.has_key("/system") and d.has_key("/")
338 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700339 return d
340
341
Doug Zongker37974732010-09-16 17:44:38 -0700342def DumpInfoDict(d):
343 for k, v in sorted(d.items()):
344 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700345
Dan Albert8b72aef2015-03-23 19:13:21 -0700346
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700347def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
348 has_ramdisk=False):
349 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700350
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700351 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
352 'sourcedir'), and turn them into a boot image. Return the image data, or
353 None if sourcedir does not appear to contains files for building the
354 requested image."""
355
356 def make_ramdisk():
357 ramdisk_img = tempfile.NamedTemporaryFile()
358
359 if os.access(fs_config_file, os.F_OK):
360 cmd = ["mkbootfs", "-f", fs_config_file,
361 os.path.join(sourcedir, "RAMDISK")]
362 else:
363 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
364 p1 = Run(cmd, stdout=subprocess.PIPE)
365 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
366
367 p2.wait()
368 p1.wait()
369 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
370 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
371
372 return ramdisk_img
373
374 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
375 return None
376
377 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700378 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700379
Doug Zongkerd5131602012-08-02 14:46:42 -0700380 if info_dict is None:
381 info_dict = OPTIONS.info_dict
382
Doug Zongkereef39442009-04-02 12:14:19 -0700383 img = tempfile.NamedTemporaryFile()
384
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700385 if has_ramdisk:
386 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700387
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800388 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
389 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
390
391 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700392
Benoit Fradina45a8682014-07-14 21:00:43 +0200393 fn = os.path.join(sourcedir, "second")
394 if os.access(fn, os.F_OK):
395 cmd.append("--second")
396 cmd.append(fn)
397
Doug Zongker171f1cd2009-06-15 22:36:37 -0700398 fn = os.path.join(sourcedir, "cmdline")
399 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700400 cmd.append("--cmdline")
401 cmd.append(open(fn).read().rstrip("\n"))
402
403 fn = os.path.join(sourcedir, "base")
404 if os.access(fn, os.F_OK):
405 cmd.append("--base")
406 cmd.append(open(fn).read().rstrip("\n"))
407
Ying Wang4de6b5b2010-08-25 14:29:34 -0700408 fn = os.path.join(sourcedir, "pagesize")
409 if os.access(fn, os.F_OK):
410 cmd.append("--pagesize")
411 cmd.append(open(fn).read().rstrip("\n"))
412
Doug Zongkerd5131602012-08-02 14:46:42 -0700413 args = info_dict.get("mkbootimg_args", None)
414 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700415 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700416
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700417 if has_ramdisk:
418 cmd.extend(["--ramdisk", ramdisk_img.name])
419
Tao Baod95e9fd2015-03-29 23:07:41 -0700420 img_unsigned = None
421 if info_dict.get("vboot", None):
422 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700423 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700424 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700425 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700426
427 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700428 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700429 assert p.returncode == 0, "mkbootimg of %s image failed" % (
430 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700431
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100432 if (info_dict.get("boot_signer", None) == "true" and
433 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700434 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700435 cmd = [OPTIONS.boot_signer_path]
436 cmd.extend(OPTIONS.boot_signer_args)
437 cmd.extend([path, img.name,
438 info_dict["verity_key"] + ".pk8",
439 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700440 p = Run(cmd, stdout=subprocess.PIPE)
441 p.communicate()
442 assert p.returncode == 0, "boot_signer of %s image failed" % path
443
Tao Baod95e9fd2015-03-29 23:07:41 -0700444 # Sign the image if vboot is non-empty.
445 elif info_dict.get("vboot", None):
446 path = "/" + os.path.basename(sourcedir).lower()
447 img_keyblock = tempfile.NamedTemporaryFile()
448 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
449 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700450 info_dict["vboot_key"] + ".vbprivk",
451 info_dict["vboot_subkey"] + ".vbprivk",
452 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700453 img.name]
454 p = Run(cmd, stdout=subprocess.PIPE)
455 p.communicate()
456 assert p.returncode == 0, "vboot_signer of %s image failed" % path
457
Tao Baof3282b42015-04-01 11:21:55 -0700458 # Clean up the temp files.
459 img_unsigned.close()
460 img_keyblock.close()
461
Doug Zongkereef39442009-04-02 12:14:19 -0700462 img.seek(os.SEEK_SET, 0)
463 data = img.read()
464
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700465 if has_ramdisk:
466 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700467 img.close()
468
469 return data
470
471
Doug Zongkerd5131602012-08-02 14:46:42 -0700472def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
473 info_dict=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700474 """Return a File object with the desired bootable image.
475
476 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
477 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
478 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700479
Doug Zongker55d93282011-01-25 17:03:34 -0800480 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
481 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700482 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800483 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700484
485 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
486 if os.path.exists(prebuilt_path):
487 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
488 return File.FromLocalFile(name, prebuilt_path)
489
490 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700491
492 if info_dict is None:
493 info_dict = OPTIONS.info_dict
494
495 # With system_root_image == "true", we don't pack ramdisk into the boot image.
496 has_ramdisk = (info_dict.get("system_root_image", None) != "true" or
497 prebuilt_name != "boot.img")
498
Doug Zongker6f1d0312014-08-22 08:07:12 -0700499 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700500 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
501 os.path.join(unpack_dir, fs_config),
502 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700503 if data:
504 return File(name, data)
505 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800506
Doug Zongkereef39442009-04-02 12:14:19 -0700507
Doug Zongker75f17362009-12-08 13:46:44 -0800508def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800509 """Unzip the given archive into a temporary directory and return the name.
510
511 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
512 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
513
514 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
515 main file), open for reading.
516 """
Doug Zongkereef39442009-04-02 12:14:19 -0700517
518 tmp = tempfile.mkdtemp(prefix="targetfiles-")
519 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800520
521 def unzip_to_dir(filename, dirname):
522 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
523 if pattern is not None:
524 cmd.append(pattern)
525 p = Run(cmd, stdout=subprocess.PIPE)
526 p.communicate()
527 if p.returncode != 0:
528 raise ExternalError("failed to unzip input target-files \"%s\"" %
529 (filename,))
530
531 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
532 if m:
533 unzip_to_dir(m.group(1), tmp)
534 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
535 filename = m.group(1)
536 else:
537 unzip_to_dir(filename, tmp)
538
539 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700540
541
542def GetKeyPasswords(keylist):
543 """Given a list of keys, prompt the user to enter passwords for
544 those which require them. Return a {key: password} dict. password
545 will be None if the key has no password."""
546
Doug Zongker8ce7c252009-05-22 13:34:54 -0700547 no_passwords = []
548 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700549 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700550 devnull = open("/dev/null", "w+b")
551 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800552 # We don't need a password for things that aren't really keys.
553 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700554 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700555 continue
556
T.R. Fullhart37e10522013-03-18 10:31:26 -0700557 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700558 "-inform", "DER", "-nocrypt"],
559 stdin=devnull.fileno(),
560 stdout=devnull.fileno(),
561 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700562 p.communicate()
563 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700564 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700565 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700566 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700567 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
568 "-inform", "DER", "-passin", "pass:"],
569 stdin=devnull.fileno(),
570 stdout=devnull.fileno(),
571 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700572 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700573 if p.returncode == 0:
574 # Encrypted key with empty string as password.
575 key_passwords[k] = ''
576 elif stderr.startswith('Error decrypting key'):
577 # Definitely encrypted key.
578 # It would have said "Error reading key" if it didn't parse correctly.
579 need_passwords.append(k)
580 else:
581 # Potentially, a type of key that openssl doesn't understand.
582 # We'll let the routines in signapk.jar handle it.
583 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700584 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700585
T.R. Fullhart37e10522013-03-18 10:31:26 -0700586 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700587 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700588 return key_passwords
589
590
Alex Klyubineb756d72015-12-04 09:21:08 -0800591def SignFile(input_name, output_name, key, password, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700592 """Sign the input_name zip/jar/apk, producing output_name. Use the
593 given key and password (the latter may be None if the key does not
594 have a password.
595
Doug Zongker951495f2009-08-14 12:44:19 -0700596 If whole_file is true, use the "-w" option to SignApk to embed a
597 signature that covers the whole file in the archive comment of the
598 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700599 """
Doug Zongker951495f2009-08-14 12:44:19 -0700600
Baligh Uddin339ee492014-09-05 11:18:07 -0700601 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700602 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
603 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700604 if whole_file:
605 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700606 cmd.extend([key + OPTIONS.public_key_suffix,
607 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800608 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700609
610 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700611 if password is not None:
612 password += "\n"
613 p.communicate(password)
614 if p.returncode != 0:
615 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
616
Doug Zongkereef39442009-04-02 12:14:19 -0700617
Doug Zongker37974732010-09-16 17:44:38 -0700618def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700619 """Check the data string passed against the max size limit, if
620 any, for the given target. Raise exception if the data is too big.
621 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700622
Dan Albert8b72aef2015-03-23 19:13:21 -0700623 if target.endswith(".img"):
624 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700625 mount_point = "/" + target
626
Ying Wangf8824af2014-06-03 14:07:27 -0700627 fs_type = None
628 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700629 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700630 if mount_point == "/userdata":
631 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700632 p = info_dict["fstab"][mount_point]
633 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800634 device = p.device
635 if "/" in device:
636 device = device[device.rfind("/")+1:]
637 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700638 if not fs_type or not limit:
639 return
Doug Zongkereef39442009-04-02 12:14:19 -0700640
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700641 if fs_type == "yaffs2":
642 # image size should be increased by 1/64th to account for the
643 # spare area (64 bytes per 2k page)
644 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800645 size = len(data)
646 pct = float(size) * 100.0 / limit
647 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
648 if pct >= 99.0:
649 raise ExternalError(msg)
650 elif pct >= 95.0:
651 print
652 print " WARNING: ", msg
653 print
654 elif OPTIONS.verbose:
655 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700656
657
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800658def ReadApkCerts(tf_zip):
659 """Given a target_files ZipFile, parse the META/apkcerts.txt file
660 and return a {package: cert} dict."""
661 certmap = {}
662 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
663 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700664 if not line:
665 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800666 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
667 r'private_key="(.*)"$', line)
668 if m:
669 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700670 public_key_suffix_len = len(OPTIONS.public_key_suffix)
671 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800672 if cert in SPECIAL_CERT_STRINGS and not privkey:
673 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700674 elif (cert.endswith(OPTIONS.public_key_suffix) and
675 privkey.endswith(OPTIONS.private_key_suffix) and
676 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
677 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800678 else:
679 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
680 return certmap
681
682
Doug Zongkereef39442009-04-02 12:14:19 -0700683COMMON_DOCSTRING = """
684 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700685 Prepend <dir>/bin to the list of places to search for binaries
686 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700687
Doug Zongker05d3dea2009-06-22 11:32:31 -0700688 -s (--device_specific) <file>
689 Path to the python module containing device-specific
690 releasetools code.
691
Doug Zongker8bec09e2009-11-30 15:37:14 -0800692 -x (--extra) <key=value>
693 Add a key/value pair to the 'extras' dict, which device-specific
694 extension code may look at.
695
Doug Zongkereef39442009-04-02 12:14:19 -0700696 -v (--verbose)
697 Show command lines being executed.
698
699 -h (--help)
700 Display this usage message and exit.
701"""
702
703def Usage(docstring):
704 print docstring.rstrip("\n")
705 print COMMON_DOCSTRING
706
707
708def ParseOptions(argv,
709 docstring,
710 extra_opts="", extra_long_opts=(),
711 extra_option_handler=None):
712 """Parse the options in argv and return any arguments that aren't
713 flags. docstring is the calling module's docstring, to be displayed
714 for errors and -h. extra_opts and extra_long_opts are for flags
715 defined by the caller, which are processed by passing them to
716 extra_option_handler."""
717
718 try:
719 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800720 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700721 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700722 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700723 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
724 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800725 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700726 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700727 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700728 Usage(docstring)
729 print "**", str(err), "**"
730 sys.exit(2)
731
Doug Zongkereef39442009-04-02 12:14:19 -0700732 for o, a in opts:
733 if o in ("-h", "--help"):
734 Usage(docstring)
735 sys.exit()
736 elif o in ("-v", "--verbose"):
737 OPTIONS.verbose = True
738 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700739 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700740 elif o in ("--signapk_path",):
741 OPTIONS.signapk_path = a
742 elif o in ("--extra_signapk_args",):
743 OPTIONS.extra_signapk_args = shlex.split(a)
744 elif o in ("--java_path",):
745 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700746 elif o in ("--java_args",):
747 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700748 elif o in ("--public_key_suffix",):
749 OPTIONS.public_key_suffix = a
750 elif o in ("--private_key_suffix",):
751 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800752 elif o in ("--boot_signer_path",):
753 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700754 elif o in ("--boot_signer_args",):
755 OPTIONS.boot_signer_args = shlex.split(a)
756 elif o in ("--verity_signer_path",):
757 OPTIONS.verity_signer_path = a
758 elif o in ("--verity_signer_args",):
759 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700760 elif o in ("-s", "--device_specific"):
761 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800762 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800763 key, value = a.split("=", 1)
764 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700765 else:
766 if extra_option_handler is None or not extra_option_handler(o, a):
767 assert False, "unknown option \"%s\"" % (o,)
768
Doug Zongker85448772014-09-09 14:59:20 -0700769 if OPTIONS.search_path:
770 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
771 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700772
773 return args
774
775
Doug Zongkerfc44a512014-08-26 13:10:25 -0700776def MakeTempFile(prefix=None, suffix=None):
777 """Make a temp file and add it to the list of things to be deleted
778 when Cleanup() is called. Return the filename."""
779 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
780 os.close(fd)
781 OPTIONS.tempfiles.append(fn)
782 return fn
783
784
Doug Zongkereef39442009-04-02 12:14:19 -0700785def Cleanup():
786 for i in OPTIONS.tempfiles:
787 if os.path.isdir(i):
788 shutil.rmtree(i)
789 else:
790 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700791
792
793class PasswordManager(object):
794 def __init__(self):
795 self.editor = os.getenv("EDITOR", None)
796 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
797
798 def GetPasswords(self, items):
799 """Get passwords corresponding to each string in 'items',
800 returning a dict. (The dict may have keys in addition to the
801 values in 'items'.)
802
803 Uses the passwords in $ANDROID_PW_FILE if available, letting the
804 user edit that file to add more needed passwords. If no editor is
805 available, or $ANDROID_PW_FILE isn't define, prompts the user
806 interactively in the ordinary way.
807 """
808
809 current = self.ReadFile()
810
811 first = True
812 while True:
813 missing = []
814 for i in items:
815 if i not in current or not current[i]:
816 missing.append(i)
817 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700818 if not missing:
819 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700820
821 for i in missing:
822 current[i] = ""
823
824 if not first:
825 print "key file %s still missing some passwords." % (self.pwfile,)
826 answer = raw_input("try to edit again? [y]> ").strip()
827 if answer and answer[0] not in 'yY':
828 raise RuntimeError("key passwords unavailable")
829 first = False
830
831 current = self.UpdateAndReadFile(current)
832
Dan Albert8b72aef2015-03-23 19:13:21 -0700833 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700834 """Prompt the user to enter a value (password) for each key in
835 'current' whose value is fales. Returns a new dict with all the
836 values.
837 """
838 result = {}
839 for k, v in sorted(current.iteritems()):
840 if v:
841 result[k] = v
842 else:
843 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700844 result[k] = getpass.getpass(
845 "Enter password for %s key> " % k).strip()
846 if result[k]:
847 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700848 return result
849
850 def UpdateAndReadFile(self, current):
851 if not self.editor or not self.pwfile:
852 return self.PromptResult(current)
853
854 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700855 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700856 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
857 f.write("# (Additional spaces are harmless.)\n\n")
858
859 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700860 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
861 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700862 f.write("[[[ %s ]]] %s\n" % (v, k))
863 if not v and first_line is None:
864 # position cursor on first line with no password.
865 first_line = i + 4
866 f.close()
867
868 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
869 _, _ = p.communicate()
870
871 return self.ReadFile()
872
873 def ReadFile(self):
874 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700875 if self.pwfile is None:
876 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700877 try:
878 f = open(self.pwfile, "r")
879 for line in f:
880 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700881 if not line or line[0] == '#':
882 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700883 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
884 if not m:
885 print "failed to parse password file: ", line
886 else:
887 result[m.group(2)] = m.group(1)
888 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700889 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700890 if e.errno != errno.ENOENT:
891 print "error reading password file: ", str(e)
892 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700893
894
Dan Albert8e0178d2015-01-27 15:53:15 -0800895def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
896 compress_type=None):
897 import datetime
898
899 # http://b/18015246
900 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
901 # for files larger than 2GiB. We can work around this by adjusting their
902 # limit. Note that `zipfile.writestr()` will not work for strings larger than
903 # 2GiB. The Python interpreter sometimes rejects strings that large (though
904 # it isn't clear to me exactly what circumstances cause this).
905 # `zipfile.write()` must be used directly to work around this.
906 #
907 # This mess can be avoided if we port to python3.
908 saved_zip64_limit = zipfile.ZIP64_LIMIT
909 zipfile.ZIP64_LIMIT = (1 << 32) - 1
910
911 if compress_type is None:
912 compress_type = zip_file.compression
913 if arcname is None:
914 arcname = filename
915
916 saved_stat = os.stat(filename)
917
918 try:
919 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
920 # file to be zipped and reset it when we're done.
921 os.chmod(filename, perms)
922
923 # Use a fixed timestamp so the output is repeatable.
924 epoch = datetime.datetime.fromtimestamp(0)
925 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
926 os.utime(filename, (timestamp, timestamp))
927
928 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
929 finally:
930 os.chmod(filename, saved_stat.st_mode)
931 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
932 zipfile.ZIP64_LIMIT = saved_zip64_limit
933
934
Tao Bao58c1b962015-05-20 09:32:18 -0700935def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700936 compress_type=None):
937 """Wrap zipfile.writestr() function to work around the zip64 limit.
938
939 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
940 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
941 when calling crc32(bytes).
942
943 But it still works fine to write a shorter string into a large zip file.
944 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
945 when we know the string won't be too long.
946 """
947
948 saved_zip64_limit = zipfile.ZIP64_LIMIT
949 zipfile.ZIP64_LIMIT = (1 << 32) - 1
950
951 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
952 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700953 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700954 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700955 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800956 else:
Tao Baof3282b42015-04-01 11:21:55 -0700957 zinfo = zinfo_or_arcname
958
959 # If compress_type is given, it overrides the value in zinfo.
960 if compress_type is not None:
961 zinfo.compress_type = compress_type
962
Tao Bao58c1b962015-05-20 09:32:18 -0700963 # If perms is given, it has a priority.
964 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700965 # If perms doesn't set the file type, mark it as a regular file.
966 if perms & 0o770000 == 0:
967 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -0700968 zinfo.external_attr = perms << 16
969
Tao Baof3282b42015-04-01 11:21:55 -0700970 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700971 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
972
Dan Albert8b72aef2015-03-23 19:13:21 -0700973 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700974 zipfile.ZIP64_LIMIT = saved_zip64_limit
975
976
977def ZipClose(zip_file):
978 # http://b/18015246
979 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
980 # central directory.
981 saved_zip64_limit = zipfile.ZIP64_LIMIT
982 zipfile.ZIP64_LIMIT = (1 << 32) - 1
983
984 zip_file.close()
985
986 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700987
988
989class DeviceSpecificParams(object):
990 module = None
991 def __init__(self, **kwargs):
992 """Keyword arguments to the constructor become attributes of this
993 object, which is passed to all functions in the device-specific
994 module."""
995 for k, v in kwargs.iteritems():
996 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800997 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700998
999 if self.module is None:
1000 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001001 if not path:
1002 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001003 try:
1004 if os.path.isdir(path):
1005 info = imp.find_module("releasetools", [path])
1006 else:
1007 d, f = os.path.split(path)
1008 b, x = os.path.splitext(f)
1009 if x == ".py":
1010 f = b
1011 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001012 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001013 self.module = imp.load_module("device_specific", *info)
1014 except ImportError:
1015 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001016
1017 def _DoCall(self, function_name, *args, **kwargs):
1018 """Call the named function in the device-specific module, passing
1019 the given args and kwargs. The first argument to the call will be
1020 the DeviceSpecific object itself. If there is no module, or the
1021 module does not define the function, return the value of the
1022 'default' kwarg (which itself defaults to None)."""
1023 if self.module is None or not hasattr(self.module, function_name):
1024 return kwargs.get("default", None)
1025 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1026
1027 def FullOTA_Assertions(self):
1028 """Called after emitting the block of assertions at the top of a
1029 full OTA package. Implementations can add whatever additional
1030 assertions they like."""
1031 return self._DoCall("FullOTA_Assertions")
1032
Doug Zongkere5ff5902012-01-17 10:55:37 -08001033 def FullOTA_InstallBegin(self):
1034 """Called at the start of full OTA installation."""
1035 return self._DoCall("FullOTA_InstallBegin")
1036
Doug Zongker05d3dea2009-06-22 11:32:31 -07001037 def FullOTA_InstallEnd(self):
1038 """Called at the end of full OTA installation; typically this is
1039 used to install the image for the device's baseband processor."""
1040 return self._DoCall("FullOTA_InstallEnd")
1041
1042 def IncrementalOTA_Assertions(self):
1043 """Called after emitting the block of assertions at the top of an
1044 incremental OTA package. Implementations can add whatever
1045 additional assertions they like."""
1046 return self._DoCall("IncrementalOTA_Assertions")
1047
Doug Zongkere5ff5902012-01-17 10:55:37 -08001048 def IncrementalOTA_VerifyBegin(self):
1049 """Called at the start of the verification phase of incremental
1050 OTA installation; additional checks can be placed here to abort
1051 the script before any changes are made."""
1052 return self._DoCall("IncrementalOTA_VerifyBegin")
1053
Doug Zongker05d3dea2009-06-22 11:32:31 -07001054 def IncrementalOTA_VerifyEnd(self):
1055 """Called at the end of the verification phase of incremental OTA
1056 installation; additional checks can be placed here to abort the
1057 script before any changes are made."""
1058 return self._DoCall("IncrementalOTA_VerifyEnd")
1059
Doug Zongkere5ff5902012-01-17 10:55:37 -08001060 def IncrementalOTA_InstallBegin(self):
1061 """Called at the start of incremental OTA installation (after
1062 verification is complete)."""
1063 return self._DoCall("IncrementalOTA_InstallBegin")
1064
Doug Zongker05d3dea2009-06-22 11:32:31 -07001065 def IncrementalOTA_InstallEnd(self):
1066 """Called at the end of incremental OTA installation; typically
1067 this is used to install the image for the device's baseband
1068 processor."""
1069 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001070
Tao Bao9bc6bb22015-11-09 16:58:28 -08001071 def VerifyOTA_Assertions(self):
1072 return self._DoCall("VerifyOTA_Assertions")
1073
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001074class File(object):
1075 def __init__(self, name, data):
1076 self.name = name
1077 self.data = data
1078 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001079 self.sha1 = sha1(data).hexdigest()
1080
1081 @classmethod
1082 def FromLocalFile(cls, name, diskname):
1083 f = open(diskname, "rb")
1084 data = f.read()
1085 f.close()
1086 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001087
1088 def WriteToTemp(self):
1089 t = tempfile.NamedTemporaryFile()
1090 t.write(self.data)
1091 t.flush()
1092 return t
1093
Geremy Condra36bd3652014-02-06 19:45:10 -08001094 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001095 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001096
1097DIFF_PROGRAM_BY_EXT = {
1098 ".gz" : "imgdiff",
1099 ".zip" : ["imgdiff", "-z"],
1100 ".jar" : ["imgdiff", "-z"],
1101 ".apk" : ["imgdiff", "-z"],
1102 ".img" : "imgdiff",
1103 }
1104
1105class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001106 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001107 self.tf = tf
1108 self.sf = sf
1109 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001110 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001111
1112 def ComputePatch(self):
1113 """Compute the patch (as a string of data) needed to turn sf into
1114 tf. Returns the same tuple as GetPatch()."""
1115
1116 tf = self.tf
1117 sf = self.sf
1118
Doug Zongker24cd2802012-08-14 16:36:15 -07001119 if self.diff_program:
1120 diff_program = self.diff_program
1121 else:
1122 ext = os.path.splitext(tf.name)[1]
1123 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001124
1125 ttemp = tf.WriteToTemp()
1126 stemp = sf.WriteToTemp()
1127
1128 ext = os.path.splitext(tf.name)[1]
1129
1130 try:
1131 ptemp = tempfile.NamedTemporaryFile()
1132 if isinstance(diff_program, list):
1133 cmd = copy.copy(diff_program)
1134 else:
1135 cmd = [diff_program]
1136 cmd.append(stemp.name)
1137 cmd.append(ttemp.name)
1138 cmd.append(ptemp.name)
1139 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001140 err = []
1141 def run():
1142 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001143 if e:
1144 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001145 th = threading.Thread(target=run)
1146 th.start()
1147 th.join(timeout=300) # 5 mins
1148 if th.is_alive():
1149 print "WARNING: diff command timed out"
1150 p.terminate()
1151 th.join(5)
1152 if th.is_alive():
1153 p.kill()
1154 th.join()
1155
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001156 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001157 print "WARNING: failure running %s:\n%s\n" % (
1158 diff_program, "".join(err))
1159 self.patch = None
1160 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001161 diff = ptemp.read()
1162 finally:
1163 ptemp.close()
1164 stemp.close()
1165 ttemp.close()
1166
1167 self.patch = diff
1168 return self.tf, self.sf, self.patch
1169
1170
1171 def GetPatch(self):
1172 """Return a tuple (target_file, source_file, patch_data).
1173 patch_data may be None if ComputePatch hasn't been called, or if
1174 computing the patch failed."""
1175 return self.tf, self.sf, self.patch
1176
1177
1178def ComputeDifferences(diffs):
1179 """Call ComputePatch on all the Difference objects in 'diffs'."""
1180 print len(diffs), "diffs to compute"
1181
1182 # Do the largest files first, to try and reduce the long-pole effect.
1183 by_size = [(i.tf.size, i) for i in diffs]
1184 by_size.sort(reverse=True)
1185 by_size = [i[1] for i in by_size]
1186
1187 lock = threading.Lock()
1188 diff_iter = iter(by_size) # accessed under lock
1189
1190 def worker():
1191 try:
1192 lock.acquire()
1193 for d in diff_iter:
1194 lock.release()
1195 start = time.time()
1196 d.ComputePatch()
1197 dur = time.time() - start
1198 lock.acquire()
1199
1200 tf, sf, patch = d.GetPatch()
1201 if sf.name == tf.name:
1202 name = tf.name
1203 else:
1204 name = "%s (%s)" % (tf.name, sf.name)
1205 if patch is None:
1206 print "patching failed! %s" % (name,)
1207 else:
1208 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1209 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1210 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001211 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001212 print e
1213 raise
1214
1215 # start worker threads; wait for them all to finish.
1216 threads = [threading.Thread(target=worker)
1217 for i in range(OPTIONS.worker_threads)]
1218 for th in threads:
1219 th.start()
1220 while threads:
1221 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001222
1223
Dan Albert8b72aef2015-03-23 19:13:21 -07001224class BlockDifference(object):
1225 def __init__(self, partition, tgt, src=None, check_first_block=False,
1226 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001227 self.tgt = tgt
1228 self.src = src
1229 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001230 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001231
Tao Bao5ece99d2015-05-12 11:42:31 -07001232 # Due to http://b/20939131, check_first_block is disabled temporarily.
1233 assert not self.check_first_block
1234
Tao Baodd2a5892015-03-12 12:32:37 -07001235 if version is None:
1236 version = 1
1237 if OPTIONS.info_dict:
1238 version = max(
1239 int(i) for i in
1240 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1241 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001242
1243 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001244 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001245 tmpdir = tempfile.mkdtemp()
1246 OPTIONS.tempfiles.append(tmpdir)
1247 self.path = os.path.join(tmpdir, partition)
1248 b.Compute(self.path)
1249
Tao Baoaac4ad52015-10-16 15:26:34 -07001250 if src is None:
1251 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1252 else:
1253 _, self.device = GetTypeAndDevice("/" + partition,
1254 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001255
1256 def WriteScript(self, script, output_zip, progress=None):
1257 if not self.src:
1258 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001259 script.Print("Patching %s image unconditionally..." % (self.partition,))
1260 else:
1261 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001262
Dan Albert8b72aef2015-03-23 19:13:21 -07001263 if progress:
1264 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001265 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001266 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001267
Tao Bao9bc6bb22015-11-09 16:58:28 -08001268 def WriteStrictVerifyScript(self, script):
1269 """Verify all the blocks in the care_map, including clobbered blocks.
1270
1271 This differs from the WriteVerifyScript() function: a) it prints different
1272 error messages; b) it doesn't allow half-way updated images to pass the
1273 verification."""
1274
1275 partition = self.partition
1276 script.Print("Verifying %s..." % (partition,))
1277 ranges = self.tgt.care_map
1278 ranges_str = ranges.to_string_raw()
1279 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1280 'ui_print(" Verified.") || '
1281 'ui_print("\\"%s\\" has unexpected contents.");' % (
1282 self.device, ranges_str,
1283 self.tgt.TotalSha1(include_clobbered_blocks=True),
1284 self.device))
1285 script.AppendExtra("")
1286
Jesse Zhao75bcea02015-01-06 10:59:53 -08001287 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001288 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001289 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001290 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001291 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001292 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1293 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001294 if self.version >= 4:
1295 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1296 'block_image_verify("%s", '
1297 'package_extract_file("%s.transfer.list"), '
1298 '"%s.new.dat", "%s.patch.dat") || '
1299 '(block_image_recover("%s", "%s") && '
1300 'block_image_verify("%s", '
1301 'package_extract_file("%s.transfer.list"), '
1302 '"%s.new.dat", "%s.patch.dat"))) then') % (
1303 self.device, ranges_str, self.src.TotalSha1(),
1304 self.device, partition, partition, partition,
1305 self.device, ranges_str,
1306 self.device, partition, partition, partition))
1307 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001308 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1309 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001310 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001311 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001312 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001313 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001314 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001315 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001316 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001317 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001318 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001319
Tao Baodd2a5892015-03-12 12:32:37 -07001320 # When generating incrementals for the system and vendor partitions,
1321 # explicitly check the first block (which contains the superblock) of
1322 # the partition to see if it's what we expect. If this check fails,
1323 # give an explicit log message about the partition having been
1324 # remounted R/W (the most likely explanation) and the need to flash to
1325 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001326 if self.check_first_block:
1327 self._CheckFirstBlock(script)
1328
Tao Baodd2a5892015-03-12 12:32:37 -07001329 # Abort the OTA update. Note that the incremental OTA cannot be applied
1330 # even if it may match the checksum of the target partition.
1331 # a) If version < 3, operations like move and erase will make changes
1332 # unconditionally and damage the partition.
1333 # b) If version >= 3, it won't even reach here.
1334 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1335 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001336
Tao Bao5fcaaef2015-06-01 13:40:49 -07001337 def _WritePostInstallVerifyScript(self, script):
1338 partition = self.partition
1339 script.Print('Verifying the updated %s image...' % (partition,))
1340 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1341 ranges = self.tgt.care_map
1342 ranges_str = ranges.to_string_raw()
1343 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1344 self.device, ranges_str,
1345 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001346
1347 # Bug: 20881595
1348 # Verify that extended blocks are really zeroed out.
1349 if self.tgt.extended:
1350 ranges_str = self.tgt.extended.to_string_raw()
1351 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1352 self.device, ranges_str,
1353 self._HashZeroBlocks(self.tgt.extended.size())))
1354 script.Print('Verified the updated %s image.' % (partition,))
1355 script.AppendExtra(
1356 'else\n'
1357 ' abort("%s partition has unexpected non-zero contents after OTA '
1358 'update");\n'
1359 'endif;' % (partition,))
1360 else:
1361 script.Print('Verified the updated %s image.' % (partition,))
1362
Tao Bao5fcaaef2015-06-01 13:40:49 -07001363 script.AppendExtra(
1364 'else\n'
1365 ' abort("%s partition has unexpected contents after OTA update");\n'
1366 'endif;' % (partition,))
1367
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001368 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001369 ZipWrite(output_zip,
1370 '{}.transfer.list'.format(self.path),
1371 '{}.transfer.list'.format(self.partition))
1372 ZipWrite(output_zip,
1373 '{}.new.dat'.format(self.path),
1374 '{}.new.dat'.format(self.partition))
1375 ZipWrite(output_zip,
1376 '{}.patch.dat'.format(self.path),
1377 '{}.patch.dat'.format(self.partition),
1378 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001379
Dan Albert8e0178d2015-01-27 15:53:15 -08001380 call = ('block_image_update("{device}", '
1381 'package_extract_file("{partition}.transfer.list"), '
1382 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1383 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001384 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001385
Dan Albert8b72aef2015-03-23 19:13:21 -07001386 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001387 data = source.ReadRangeSet(ranges)
1388 ctx = sha1()
1389
1390 for p in data:
1391 ctx.update(p)
1392
1393 return ctx.hexdigest()
1394
Tao Baoe9b61912015-07-09 17:37:49 -07001395 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1396 """Return the hash value for all zero blocks."""
1397 zero_block = '\x00' * 4096
1398 ctx = sha1()
1399 for _ in range(num_blocks):
1400 ctx.update(zero_block)
1401
1402 return ctx.hexdigest()
1403
Tao Bao5ece99d2015-05-12 11:42:31 -07001404 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1405 # remounting R/W. Will change the checking to a finer-grained way to
1406 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001407 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001408 r = rangelib.RangeSet((0, 1))
1409 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001410
1411 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1412 'abort("%s has been remounted R/W; '
1413 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001414 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001415 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001416
1417DataImage = blockimgdiff.DataImage
1418
1419
Doug Zongker96a57e72010-09-26 14:57:41 -07001420# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001421PARTITION_TYPES = {
1422 "yaffs2": "MTD",
1423 "mtd": "MTD",
1424 "ext4": "EMMC",
1425 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001426 "f2fs": "EMMC",
1427 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001428}
Doug Zongker96a57e72010-09-26 14:57:41 -07001429
1430def GetTypeAndDevice(mount_point, info):
1431 fstab = info["fstab"]
1432 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001433 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1434 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001435 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001436 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001437
1438
1439def ParseCertificate(data):
1440 """Parse a PEM-format certificate."""
1441 cert = []
1442 save = False
1443 for line in data.split("\n"):
1444 if "--END CERTIFICATE--" in line:
1445 break
1446 if save:
1447 cert.append(line)
1448 if "--BEGIN CERTIFICATE--" in line:
1449 save = True
1450 cert = "".join(cert).decode('base64')
1451 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001452
Doug Zongker412c02f2014-02-13 10:58:24 -08001453def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1454 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001455 """Generate a binary patch that creates the recovery image starting
1456 with the boot image. (Most of the space in these images is just the
1457 kernel, which is identical for the two, so the resulting patch
1458 should be efficient.) Add it to the output zip, along with a shell
1459 script that is run from init.rc on first boot to actually do the
1460 patching and install the new recovery image.
1461
1462 recovery_img and boot_img should be File objects for the
1463 corresponding images. info should be the dictionary returned by
1464 common.LoadInfoDict() on the input target_files.
1465 """
1466
Doug Zongker412c02f2014-02-13 10:58:24 -08001467 if info_dict is None:
1468 info_dict = OPTIONS.info_dict
1469
Tao Baof2cffbd2015-07-22 12:33:18 -07001470 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001471 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001472
Tao Baof2cffbd2015-07-22 12:33:18 -07001473 if full_recovery_image:
1474 output_sink("etc/recovery.img", recovery_img.data)
1475
1476 else:
1477 diff_program = ["imgdiff"]
1478 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1479 if os.path.exists(path):
1480 diff_program.append("-b")
1481 diff_program.append(path)
1482 bonus_args = "-b /system/etc/recovery-resource.dat"
1483 else:
1484 bonus_args = ""
1485
1486 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1487 _, _, patch = d.ComputePatch()
1488 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001489
Dan Albertebb19aa2015-03-27 19:11:53 -07001490 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001491 # The following GetTypeAndDevice()s need to use the path in the target
1492 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001493 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1494 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1495 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001496 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001497
Tao Baof2cffbd2015-07-22 12:33:18 -07001498 if full_recovery_image:
1499 sh = """#!/system/bin/sh
1500if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1501 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"
1502else
1503 log -t recovery "Recovery image already installed"
1504fi
1505""" % {'type': recovery_type,
1506 'device': recovery_device,
1507 'sha1': recovery_img.sha1,
1508 'size': recovery_img.size}
1509 else:
1510 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001511if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1512 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"
1513else
1514 log -t recovery "Recovery image already installed"
1515fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001516""" % {'boot_size': boot_img.size,
1517 'boot_sha1': boot_img.sha1,
1518 'recovery_size': recovery_img.size,
1519 'recovery_sha1': recovery_img.sha1,
1520 'boot_type': boot_type,
1521 'boot_device': boot_device,
1522 'recovery_type': recovery_type,
1523 'recovery_device': recovery_device,
1524 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001525
1526 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001527 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001528 # target-files expects it to be, and put it there.
1529 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001530 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001531 if system_root_image:
1532 init_rc_dir = os.path.join(input_dir, "ROOT")
1533 else:
1534 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001535 init_rc_files = os.listdir(init_rc_dir)
1536 for init_rc_file in init_rc_files:
1537 if (not init_rc_file.startswith('init.') or
1538 not init_rc_file.endswith('.rc')):
1539 continue
1540
1541 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001542 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001543 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001544 if m:
1545 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001546 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001547 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001548
1549 if found:
1550 break
1551
1552 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001553
1554 output_sink(sh_location, sh)