blob: 872dd4c8e282dd1feaaf88c08f5c6620670312db [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 Bao7a5bf8a2015-07-21 18:01:20 -0700214 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
215 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800216 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700217 return d
218
Doug Zongkerc9253822014-02-04 12:17:58 -0800219def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700220 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800221 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700222 except KeyError:
223 print "Warning: could not find SYSTEM/build.prop in %s" % zip
224 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700225 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700226
Michael Runge6e836112014-04-15 17:40:21 -0700227def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700228 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700229 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700230 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 if not line or line.startswith("#"):
232 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700233 if "=" in line:
234 name, value = line.split("=", 1)
235 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700236 return d
237
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700238def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700239 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700240 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700241 self.mount_point = mount_point
242 self.fs_type = fs_type
243 self.device = device
244 self.length = length
245 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700246 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700247
248 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800249 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700250 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800251 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700252 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700253
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800254 if fstab_version == 1:
255 d = {}
256 for line in data.split("\n"):
257 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700258 if not line or line.startswith("#"):
259 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800260 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700261 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800262 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800263 options = None
264 if len(pieces) >= 4:
265 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700266 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800267 if len(pieces) >= 5:
268 options = pieces[4]
269 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700270 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800271 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800272 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700273 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700274
Dan Albert8b72aef2015-03-23 19:13:21 -0700275 mount_point = pieces[0]
276 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800277 if options:
278 options = options.split(",")
279 for i in options:
280 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700281 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800282 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700283 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800284
Dan Albert8b72aef2015-03-23 19:13:21 -0700285 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
286 device=pieces[2], length=length,
287 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800288
289 elif fstab_version == 2:
290 d = {}
291 for line in data.split("\n"):
292 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700293 if not line or line.startswith("#"):
294 continue
Tao Bao548eb762015-06-10 12:32:41 -0700295 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800296 pieces = line.split()
297 if len(pieces) != 5:
298 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
299
300 # Ignore entries that are managed by vold
301 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700302 if "voldmanaged=" in options:
303 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800304
305 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700306 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800307 options = options.split(",")
308 for i in options:
309 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700310 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800311 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800312 # Ignore all unknown options in the unified fstab
313 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800314
Tao Bao548eb762015-06-10 12:32:41 -0700315 mount_flags = pieces[3]
316 # Honor the SELinux context if present.
317 context = None
318 for i in mount_flags.split(","):
319 if i.startswith("context="):
320 context = i
321
Dan Albert8b72aef2015-03-23 19:13:21 -0700322 mount_point = pieces[1]
323 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700324 device=pieces[0], length=length,
325 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800326
327 else:
328 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
329
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700330 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700331 # system. Other areas assume system is always at "/system" so point /system
332 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700333 if system_root_image:
334 assert not d.has_key("/system") and d.has_key("/")
335 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700336 return d
337
338
Doug Zongker37974732010-09-16 17:44:38 -0700339def DumpInfoDict(d):
340 for k, v in sorted(d.items()):
341 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700342
Dan Albert8b72aef2015-03-23 19:13:21 -0700343
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700344def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
345 has_ramdisk=False):
346 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700347
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700348 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
349 'sourcedir'), and turn them into a boot image. Return the image data, or
350 None if sourcedir does not appear to contains files for building the
351 requested image."""
352
353 def make_ramdisk():
354 ramdisk_img = tempfile.NamedTemporaryFile()
355
356 if os.access(fs_config_file, os.F_OK):
357 cmd = ["mkbootfs", "-f", fs_config_file,
358 os.path.join(sourcedir, "RAMDISK")]
359 else:
360 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
361 p1 = Run(cmd, stdout=subprocess.PIPE)
362 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
363
364 p2.wait()
365 p1.wait()
366 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
367 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
368
369 return ramdisk_img
370
371 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
372 return None
373
374 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700375 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700376
Doug Zongkerd5131602012-08-02 14:46:42 -0700377 if info_dict is None:
378 info_dict = OPTIONS.info_dict
379
Doug Zongkereef39442009-04-02 12:14:19 -0700380 img = tempfile.NamedTemporaryFile()
381
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700382 if has_ramdisk:
383 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700384
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800385 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
386 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
387
388 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700389
Benoit Fradina45a8682014-07-14 21:00:43 +0200390 fn = os.path.join(sourcedir, "second")
391 if os.access(fn, os.F_OK):
392 cmd.append("--second")
393 cmd.append(fn)
394
Doug Zongker171f1cd2009-06-15 22:36:37 -0700395 fn = os.path.join(sourcedir, "cmdline")
396 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700397 cmd.append("--cmdline")
398 cmd.append(open(fn).read().rstrip("\n"))
399
400 fn = os.path.join(sourcedir, "base")
401 if os.access(fn, os.F_OK):
402 cmd.append("--base")
403 cmd.append(open(fn).read().rstrip("\n"))
404
Ying Wang4de6b5b2010-08-25 14:29:34 -0700405 fn = os.path.join(sourcedir, "pagesize")
406 if os.access(fn, os.F_OK):
407 cmd.append("--pagesize")
408 cmd.append(open(fn).read().rstrip("\n"))
409
Doug Zongkerd5131602012-08-02 14:46:42 -0700410 args = info_dict.get("mkbootimg_args", None)
411 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700412 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700413
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700414 if has_ramdisk:
415 cmd.extend(["--ramdisk", ramdisk_img.name])
416
Tao Baod95e9fd2015-03-29 23:07:41 -0700417 img_unsigned = None
418 if info_dict.get("vboot", None):
419 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700420 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700421 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700422 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700423
424 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700425 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700426 assert p.returncode == 0, "mkbootimg of %s image failed" % (
427 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700428
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100429 if (info_dict.get("boot_signer", None) == "true" and
430 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700431 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700432 cmd = [OPTIONS.boot_signer_path]
433 cmd.extend(OPTIONS.boot_signer_args)
434 cmd.extend([path, img.name,
435 info_dict["verity_key"] + ".pk8",
436 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700437 p = Run(cmd, stdout=subprocess.PIPE)
438 p.communicate()
439 assert p.returncode == 0, "boot_signer of %s image failed" % path
440
Tao Baod95e9fd2015-03-29 23:07:41 -0700441 # Sign the image if vboot is non-empty.
442 elif info_dict.get("vboot", None):
443 path = "/" + os.path.basename(sourcedir).lower()
444 img_keyblock = tempfile.NamedTemporaryFile()
445 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
446 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700447 info_dict["vboot_key"] + ".vbprivk",
448 info_dict["vboot_subkey"] + ".vbprivk",
449 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700450 img.name]
451 p = Run(cmd, stdout=subprocess.PIPE)
452 p.communicate()
453 assert p.returncode == 0, "vboot_signer of %s image failed" % path
454
Tao Baof3282b42015-04-01 11:21:55 -0700455 # Clean up the temp files.
456 img_unsigned.close()
457 img_keyblock.close()
458
Doug Zongkereef39442009-04-02 12:14:19 -0700459 img.seek(os.SEEK_SET, 0)
460 data = img.read()
461
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700462 if has_ramdisk:
463 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700464 img.close()
465
466 return data
467
468
Doug Zongkerd5131602012-08-02 14:46:42 -0700469def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
470 info_dict=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700471 """Return a File object with the desired bootable image.
472
473 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
474 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
475 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700476
Doug Zongker55d93282011-01-25 17:03:34 -0800477 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
478 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700479 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800480 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700481
482 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
483 if os.path.exists(prebuilt_path):
484 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
485 return File.FromLocalFile(name, prebuilt_path)
486
487 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700488
489 if info_dict is None:
490 info_dict = OPTIONS.info_dict
491
492 # With system_root_image == "true", we don't pack ramdisk into the boot image.
493 has_ramdisk = (info_dict.get("system_root_image", None) != "true" or
494 prebuilt_name != "boot.img")
495
Doug Zongker6f1d0312014-08-22 08:07:12 -0700496 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700497 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
498 os.path.join(unpack_dir, fs_config),
499 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700500 if data:
501 return File(name, data)
502 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800503
Doug Zongkereef39442009-04-02 12:14:19 -0700504
Doug Zongker75f17362009-12-08 13:46:44 -0800505def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800506 """Unzip the given archive into a temporary directory and return the name.
507
508 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
509 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
510
511 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
512 main file), open for reading.
513 """
Doug Zongkereef39442009-04-02 12:14:19 -0700514
515 tmp = tempfile.mkdtemp(prefix="targetfiles-")
516 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800517
518 def unzip_to_dir(filename, dirname):
519 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
520 if pattern is not None:
521 cmd.append(pattern)
522 p = Run(cmd, stdout=subprocess.PIPE)
523 p.communicate()
524 if p.returncode != 0:
525 raise ExternalError("failed to unzip input target-files \"%s\"" %
526 (filename,))
527
528 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
529 if m:
530 unzip_to_dir(m.group(1), tmp)
531 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
532 filename = m.group(1)
533 else:
534 unzip_to_dir(filename, tmp)
535
536 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700537
538
539def GetKeyPasswords(keylist):
540 """Given a list of keys, prompt the user to enter passwords for
541 those which require them. Return a {key: password} dict. password
542 will be None if the key has no password."""
543
Doug Zongker8ce7c252009-05-22 13:34:54 -0700544 no_passwords = []
545 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700546 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700547 devnull = open("/dev/null", "w+b")
548 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800549 # We don't need a password for things that aren't really keys.
550 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700551 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700552 continue
553
T.R. Fullhart37e10522013-03-18 10:31:26 -0700554 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700555 "-inform", "DER", "-nocrypt"],
556 stdin=devnull.fileno(),
557 stdout=devnull.fileno(),
558 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700559 p.communicate()
560 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700561 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700562 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700563 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700564 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
565 "-inform", "DER", "-passin", "pass:"],
566 stdin=devnull.fileno(),
567 stdout=devnull.fileno(),
568 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700569 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700570 if p.returncode == 0:
571 # Encrypted key with empty string as password.
572 key_passwords[k] = ''
573 elif stderr.startswith('Error decrypting key'):
574 # Definitely encrypted key.
575 # It would have said "Error reading key" if it didn't parse correctly.
576 need_passwords.append(k)
577 else:
578 # Potentially, a type of key that openssl doesn't understand.
579 # We'll let the routines in signapk.jar handle it.
580 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700581 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700582
T.R. Fullhart37e10522013-03-18 10:31:26 -0700583 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700584 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700585 return key_passwords
586
587
Doug Zongker951495f2009-08-14 12:44:19 -0700588def SignFile(input_name, output_name, key, password, align=None,
589 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700590 """Sign the input_name zip/jar/apk, producing output_name. Use the
591 given key and password (the latter may be None if the key does not
592 have a password.
593
594 If align is an integer > 1, zipalign is run to align stored files in
595 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700596
597 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
Doug Zongkereef39442009-04-02 12:14:19 -0700602 if align == 0 or align == 1:
603 align = None
604
605 if align:
606 temp = tempfile.NamedTemporaryFile()
607 sign_name = temp.name
608 else:
609 sign_name = output_name
610
Baligh Uddin339ee492014-09-05 11:18:07 -0700611 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700612 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
613 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700614 if whole_file:
615 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700616 cmd.extend([key + OPTIONS.public_key_suffix,
617 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700618 input_name, sign_name])
619
620 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700621 if password is not None:
622 password += "\n"
623 p.communicate(password)
624 if p.returncode != 0:
625 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
626
627 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700628 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700629 p.communicate()
630 if p.returncode != 0:
631 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
632 temp.close()
633
634
Doug Zongker37974732010-09-16 17:44:38 -0700635def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700636 """Check the data string passed against the max size limit, if
637 any, for the given target. Raise exception if the data is too big.
638 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700639
Dan Albert8b72aef2015-03-23 19:13:21 -0700640 if target.endswith(".img"):
641 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700642 mount_point = "/" + target
643
Ying Wangf8824af2014-06-03 14:07:27 -0700644 fs_type = None
645 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700646 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700647 if mount_point == "/userdata":
648 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700649 p = info_dict["fstab"][mount_point]
650 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800651 device = p.device
652 if "/" in device:
653 device = device[device.rfind("/")+1:]
654 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700655 if not fs_type or not limit:
656 return
Doug Zongkereef39442009-04-02 12:14:19 -0700657
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700658 if fs_type == "yaffs2":
659 # image size should be increased by 1/64th to account for the
660 # spare area (64 bytes per 2k page)
661 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800662 size = len(data)
663 pct = float(size) * 100.0 / limit
664 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
665 if pct >= 99.0:
666 raise ExternalError(msg)
667 elif pct >= 95.0:
668 print
669 print " WARNING: ", msg
670 print
671 elif OPTIONS.verbose:
672 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700673
674
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800675def ReadApkCerts(tf_zip):
676 """Given a target_files ZipFile, parse the META/apkcerts.txt file
677 and return a {package: cert} dict."""
678 certmap = {}
679 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
680 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700681 if not line:
682 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800683 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
684 r'private_key="(.*)"$', line)
685 if m:
686 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700687 public_key_suffix_len = len(OPTIONS.public_key_suffix)
688 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800689 if cert in SPECIAL_CERT_STRINGS and not privkey:
690 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700691 elif (cert.endswith(OPTIONS.public_key_suffix) and
692 privkey.endswith(OPTIONS.private_key_suffix) and
693 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
694 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800695 else:
696 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
697 return certmap
698
699
Doug Zongkereef39442009-04-02 12:14:19 -0700700COMMON_DOCSTRING = """
701 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700702 Prepend <dir>/bin to the list of places to search for binaries
703 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700704
Doug Zongker05d3dea2009-06-22 11:32:31 -0700705 -s (--device_specific) <file>
706 Path to the python module containing device-specific
707 releasetools code.
708
Doug Zongker8bec09e2009-11-30 15:37:14 -0800709 -x (--extra) <key=value>
710 Add a key/value pair to the 'extras' dict, which device-specific
711 extension code may look at.
712
Doug Zongkereef39442009-04-02 12:14:19 -0700713 -v (--verbose)
714 Show command lines being executed.
715
716 -h (--help)
717 Display this usage message and exit.
718"""
719
720def Usage(docstring):
721 print docstring.rstrip("\n")
722 print COMMON_DOCSTRING
723
724
725def ParseOptions(argv,
726 docstring,
727 extra_opts="", extra_long_opts=(),
728 extra_option_handler=None):
729 """Parse the options in argv and return any arguments that aren't
730 flags. docstring is the calling module's docstring, to be displayed
731 for errors and -h. extra_opts and extra_long_opts are for flags
732 defined by the caller, which are processed by passing them to
733 extra_option_handler."""
734
735 try:
736 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800737 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700738 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700739 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700740 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
741 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800742 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700743 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700744 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700745 Usage(docstring)
746 print "**", str(err), "**"
747 sys.exit(2)
748
Doug Zongkereef39442009-04-02 12:14:19 -0700749 for o, a in opts:
750 if o in ("-h", "--help"):
751 Usage(docstring)
752 sys.exit()
753 elif o in ("-v", "--verbose"):
754 OPTIONS.verbose = True
755 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700756 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700757 elif o in ("--signapk_path",):
758 OPTIONS.signapk_path = a
759 elif o in ("--extra_signapk_args",):
760 OPTIONS.extra_signapk_args = shlex.split(a)
761 elif o in ("--java_path",):
762 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700763 elif o in ("--java_args",):
764 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700765 elif o in ("--public_key_suffix",):
766 OPTIONS.public_key_suffix = a
767 elif o in ("--private_key_suffix",):
768 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800769 elif o in ("--boot_signer_path",):
770 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700771 elif o in ("--boot_signer_args",):
772 OPTIONS.boot_signer_args = shlex.split(a)
773 elif o in ("--verity_signer_path",):
774 OPTIONS.verity_signer_path = a
775 elif o in ("--verity_signer_args",):
776 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700777 elif o in ("-s", "--device_specific"):
778 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800779 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800780 key, value = a.split("=", 1)
781 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700782 else:
783 if extra_option_handler is None or not extra_option_handler(o, a):
784 assert False, "unknown option \"%s\"" % (o,)
785
Doug Zongker85448772014-09-09 14:59:20 -0700786 if OPTIONS.search_path:
787 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
788 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700789
790 return args
791
792
Doug Zongkerfc44a512014-08-26 13:10:25 -0700793def MakeTempFile(prefix=None, suffix=None):
794 """Make a temp file and add it to the list of things to be deleted
795 when Cleanup() is called. Return the filename."""
796 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
797 os.close(fd)
798 OPTIONS.tempfiles.append(fn)
799 return fn
800
801
Doug Zongkereef39442009-04-02 12:14:19 -0700802def Cleanup():
803 for i in OPTIONS.tempfiles:
804 if os.path.isdir(i):
805 shutil.rmtree(i)
806 else:
807 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700808
809
810class PasswordManager(object):
811 def __init__(self):
812 self.editor = os.getenv("EDITOR", None)
813 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
814
815 def GetPasswords(self, items):
816 """Get passwords corresponding to each string in 'items',
817 returning a dict. (The dict may have keys in addition to the
818 values in 'items'.)
819
820 Uses the passwords in $ANDROID_PW_FILE if available, letting the
821 user edit that file to add more needed passwords. If no editor is
822 available, or $ANDROID_PW_FILE isn't define, prompts the user
823 interactively in the ordinary way.
824 """
825
826 current = self.ReadFile()
827
828 first = True
829 while True:
830 missing = []
831 for i in items:
832 if i not in current or not current[i]:
833 missing.append(i)
834 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700835 if not missing:
836 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700837
838 for i in missing:
839 current[i] = ""
840
841 if not first:
842 print "key file %s still missing some passwords." % (self.pwfile,)
843 answer = raw_input("try to edit again? [y]> ").strip()
844 if answer and answer[0] not in 'yY':
845 raise RuntimeError("key passwords unavailable")
846 first = False
847
848 current = self.UpdateAndReadFile(current)
849
Dan Albert8b72aef2015-03-23 19:13:21 -0700850 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700851 """Prompt the user to enter a value (password) for each key in
852 'current' whose value is fales. Returns a new dict with all the
853 values.
854 """
855 result = {}
856 for k, v in sorted(current.iteritems()):
857 if v:
858 result[k] = v
859 else:
860 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700861 result[k] = getpass.getpass(
862 "Enter password for %s key> " % k).strip()
863 if result[k]:
864 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700865 return result
866
867 def UpdateAndReadFile(self, current):
868 if not self.editor or not self.pwfile:
869 return self.PromptResult(current)
870
871 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700872 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700873 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
874 f.write("# (Additional spaces are harmless.)\n\n")
875
876 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700877 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
878 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700879 f.write("[[[ %s ]]] %s\n" % (v, k))
880 if not v and first_line is None:
881 # position cursor on first line with no password.
882 first_line = i + 4
883 f.close()
884
885 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
886 _, _ = p.communicate()
887
888 return self.ReadFile()
889
890 def ReadFile(self):
891 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700892 if self.pwfile is None:
893 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700894 try:
895 f = open(self.pwfile, "r")
896 for line in f:
897 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700898 if not line or line[0] == '#':
899 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700900 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
901 if not m:
902 print "failed to parse password file: ", line
903 else:
904 result[m.group(2)] = m.group(1)
905 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700906 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700907 if e.errno != errno.ENOENT:
908 print "error reading password file: ", str(e)
909 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700910
911
Dan Albert8e0178d2015-01-27 15:53:15 -0800912def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
913 compress_type=None):
914 import datetime
915
916 # http://b/18015246
917 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
918 # for files larger than 2GiB. We can work around this by adjusting their
919 # limit. Note that `zipfile.writestr()` will not work for strings larger than
920 # 2GiB. The Python interpreter sometimes rejects strings that large (though
921 # it isn't clear to me exactly what circumstances cause this).
922 # `zipfile.write()` must be used directly to work around this.
923 #
924 # This mess can be avoided if we port to python3.
925 saved_zip64_limit = zipfile.ZIP64_LIMIT
926 zipfile.ZIP64_LIMIT = (1 << 32) - 1
927
928 if compress_type is None:
929 compress_type = zip_file.compression
930 if arcname is None:
931 arcname = filename
932
933 saved_stat = os.stat(filename)
934
935 try:
936 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
937 # file to be zipped and reset it when we're done.
938 os.chmod(filename, perms)
939
940 # Use a fixed timestamp so the output is repeatable.
941 epoch = datetime.datetime.fromtimestamp(0)
942 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
943 os.utime(filename, (timestamp, timestamp))
944
945 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
946 finally:
947 os.chmod(filename, saved_stat.st_mode)
948 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
949 zipfile.ZIP64_LIMIT = saved_zip64_limit
950
951
Tao Bao58c1b962015-05-20 09:32:18 -0700952def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700953 compress_type=None):
954 """Wrap zipfile.writestr() function to work around the zip64 limit.
955
956 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
957 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
958 when calling crc32(bytes).
959
960 But it still works fine to write a shorter string into a large zip file.
961 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
962 when we know the string won't be too long.
963 """
964
965 saved_zip64_limit = zipfile.ZIP64_LIMIT
966 zipfile.ZIP64_LIMIT = (1 << 32) - 1
967
968 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
969 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700970 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700971 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700972 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800973 else:
Tao Baof3282b42015-04-01 11:21:55 -0700974 zinfo = zinfo_or_arcname
975
976 # If compress_type is given, it overrides the value in zinfo.
977 if compress_type is not None:
978 zinfo.compress_type = compress_type
979
Tao Bao58c1b962015-05-20 09:32:18 -0700980 # If perms is given, it has a priority.
981 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700982 # If perms doesn't set the file type, mark it as a regular file.
983 if perms & 0o770000 == 0:
984 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -0700985 zinfo.external_attr = perms << 16
986
Tao Baof3282b42015-04-01 11:21:55 -0700987 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700988 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
989
Dan Albert8b72aef2015-03-23 19:13:21 -0700990 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700991 zipfile.ZIP64_LIMIT = saved_zip64_limit
992
993
994def ZipClose(zip_file):
995 # http://b/18015246
996 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
997 # central directory.
998 saved_zip64_limit = zipfile.ZIP64_LIMIT
999 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1000
1001 zip_file.close()
1002
1003 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001004
1005
1006class DeviceSpecificParams(object):
1007 module = None
1008 def __init__(self, **kwargs):
1009 """Keyword arguments to the constructor become attributes of this
1010 object, which is passed to all functions in the device-specific
1011 module."""
1012 for k, v in kwargs.iteritems():
1013 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001014 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001015
1016 if self.module is None:
1017 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001018 if not path:
1019 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001020 try:
1021 if os.path.isdir(path):
1022 info = imp.find_module("releasetools", [path])
1023 else:
1024 d, f = os.path.split(path)
1025 b, x = os.path.splitext(f)
1026 if x == ".py":
1027 f = b
1028 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001029 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001030 self.module = imp.load_module("device_specific", *info)
1031 except ImportError:
1032 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001033
1034 def _DoCall(self, function_name, *args, **kwargs):
1035 """Call the named function in the device-specific module, passing
1036 the given args and kwargs. The first argument to the call will be
1037 the DeviceSpecific object itself. If there is no module, or the
1038 module does not define the function, return the value of the
1039 'default' kwarg (which itself defaults to None)."""
1040 if self.module is None or not hasattr(self.module, function_name):
1041 return kwargs.get("default", None)
1042 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1043
1044 def FullOTA_Assertions(self):
1045 """Called after emitting the block of assertions at the top of a
1046 full OTA package. Implementations can add whatever additional
1047 assertions they like."""
1048 return self._DoCall("FullOTA_Assertions")
1049
Doug Zongkere5ff5902012-01-17 10:55:37 -08001050 def FullOTA_InstallBegin(self):
1051 """Called at the start of full OTA installation."""
1052 return self._DoCall("FullOTA_InstallBegin")
1053
Doug Zongker05d3dea2009-06-22 11:32:31 -07001054 def FullOTA_InstallEnd(self):
1055 """Called at the end of full OTA installation; typically this is
1056 used to install the image for the device's baseband processor."""
1057 return self._DoCall("FullOTA_InstallEnd")
1058
1059 def IncrementalOTA_Assertions(self):
1060 """Called after emitting the block of assertions at the top of an
1061 incremental OTA package. Implementations can add whatever
1062 additional assertions they like."""
1063 return self._DoCall("IncrementalOTA_Assertions")
1064
Doug Zongkere5ff5902012-01-17 10:55:37 -08001065 def IncrementalOTA_VerifyBegin(self):
1066 """Called at the start of the verification phase of incremental
1067 OTA installation; additional checks can be placed here to abort
1068 the script before any changes are made."""
1069 return self._DoCall("IncrementalOTA_VerifyBegin")
1070
Doug Zongker05d3dea2009-06-22 11:32:31 -07001071 def IncrementalOTA_VerifyEnd(self):
1072 """Called at the end of the verification phase of incremental OTA
1073 installation; additional checks can be placed here to abort the
1074 script before any changes are made."""
1075 return self._DoCall("IncrementalOTA_VerifyEnd")
1076
Doug Zongkere5ff5902012-01-17 10:55:37 -08001077 def IncrementalOTA_InstallBegin(self):
1078 """Called at the start of incremental OTA installation (after
1079 verification is complete)."""
1080 return self._DoCall("IncrementalOTA_InstallBegin")
1081
Doug Zongker05d3dea2009-06-22 11:32:31 -07001082 def IncrementalOTA_InstallEnd(self):
1083 """Called at the end of incremental OTA installation; typically
1084 this is used to install the image for the device's baseband
1085 processor."""
1086 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001087
Tao Bao9bc6bb22015-11-09 16:58:28 -08001088 def VerifyOTA_Assertions(self):
1089 return self._DoCall("VerifyOTA_Assertions")
1090
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001091class File(object):
1092 def __init__(self, name, data):
1093 self.name = name
1094 self.data = data
1095 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001096 self.sha1 = sha1(data).hexdigest()
1097
1098 @classmethod
1099 def FromLocalFile(cls, name, diskname):
1100 f = open(diskname, "rb")
1101 data = f.read()
1102 f.close()
1103 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001104
1105 def WriteToTemp(self):
1106 t = tempfile.NamedTemporaryFile()
1107 t.write(self.data)
1108 t.flush()
1109 return t
1110
Geremy Condra36bd3652014-02-06 19:45:10 -08001111 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001112 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001113
1114DIFF_PROGRAM_BY_EXT = {
1115 ".gz" : "imgdiff",
1116 ".zip" : ["imgdiff", "-z"],
1117 ".jar" : ["imgdiff", "-z"],
1118 ".apk" : ["imgdiff", "-z"],
1119 ".img" : "imgdiff",
1120 }
1121
1122class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001123 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001124 self.tf = tf
1125 self.sf = sf
1126 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001127 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001128
1129 def ComputePatch(self):
1130 """Compute the patch (as a string of data) needed to turn sf into
1131 tf. Returns the same tuple as GetPatch()."""
1132
1133 tf = self.tf
1134 sf = self.sf
1135
Doug Zongker24cd2802012-08-14 16:36:15 -07001136 if self.diff_program:
1137 diff_program = self.diff_program
1138 else:
1139 ext = os.path.splitext(tf.name)[1]
1140 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001141
1142 ttemp = tf.WriteToTemp()
1143 stemp = sf.WriteToTemp()
1144
1145 ext = os.path.splitext(tf.name)[1]
1146
1147 try:
1148 ptemp = tempfile.NamedTemporaryFile()
1149 if isinstance(diff_program, list):
1150 cmd = copy.copy(diff_program)
1151 else:
1152 cmd = [diff_program]
1153 cmd.append(stemp.name)
1154 cmd.append(ttemp.name)
1155 cmd.append(ptemp.name)
1156 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001157 err = []
1158 def run():
1159 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001160 if e:
1161 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001162 th = threading.Thread(target=run)
1163 th.start()
1164 th.join(timeout=300) # 5 mins
1165 if th.is_alive():
1166 print "WARNING: diff command timed out"
1167 p.terminate()
1168 th.join(5)
1169 if th.is_alive():
1170 p.kill()
1171 th.join()
1172
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001173 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001174 print "WARNING: failure running %s:\n%s\n" % (
1175 diff_program, "".join(err))
1176 self.patch = None
1177 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001178 diff = ptemp.read()
1179 finally:
1180 ptemp.close()
1181 stemp.close()
1182 ttemp.close()
1183
1184 self.patch = diff
1185 return self.tf, self.sf, self.patch
1186
1187
1188 def GetPatch(self):
1189 """Return a tuple (target_file, source_file, patch_data).
1190 patch_data may be None if ComputePatch hasn't been called, or if
1191 computing the patch failed."""
1192 return self.tf, self.sf, self.patch
1193
1194
1195def ComputeDifferences(diffs):
1196 """Call ComputePatch on all the Difference objects in 'diffs'."""
1197 print len(diffs), "diffs to compute"
1198
1199 # Do the largest files first, to try and reduce the long-pole effect.
1200 by_size = [(i.tf.size, i) for i in diffs]
1201 by_size.sort(reverse=True)
1202 by_size = [i[1] for i in by_size]
1203
1204 lock = threading.Lock()
1205 diff_iter = iter(by_size) # accessed under lock
1206
1207 def worker():
1208 try:
1209 lock.acquire()
1210 for d in diff_iter:
1211 lock.release()
1212 start = time.time()
1213 d.ComputePatch()
1214 dur = time.time() - start
1215 lock.acquire()
1216
1217 tf, sf, patch = d.GetPatch()
1218 if sf.name == tf.name:
1219 name = tf.name
1220 else:
1221 name = "%s (%s)" % (tf.name, sf.name)
1222 if patch is None:
1223 print "patching failed! %s" % (name,)
1224 else:
1225 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1226 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1227 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001228 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001229 print e
1230 raise
1231
1232 # start worker threads; wait for them all to finish.
1233 threads = [threading.Thread(target=worker)
1234 for i in range(OPTIONS.worker_threads)]
1235 for th in threads:
1236 th.start()
1237 while threads:
1238 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001239
1240
Dan Albert8b72aef2015-03-23 19:13:21 -07001241class BlockDifference(object):
1242 def __init__(self, partition, tgt, src=None, check_first_block=False,
1243 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001244 self.tgt = tgt
1245 self.src = src
1246 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001247 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001248
Tao Bao5ece99d2015-05-12 11:42:31 -07001249 # Due to http://b/20939131, check_first_block is disabled temporarily.
1250 assert not self.check_first_block
1251
Tao Baodd2a5892015-03-12 12:32:37 -07001252 if version is None:
1253 version = 1
1254 if OPTIONS.info_dict:
1255 version = max(
1256 int(i) for i in
1257 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1258 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001259
1260 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001261 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001262 tmpdir = tempfile.mkdtemp()
1263 OPTIONS.tempfiles.append(tmpdir)
1264 self.path = os.path.join(tmpdir, partition)
1265 b.Compute(self.path)
1266
Tao Baoaac4ad52015-10-16 15:26:34 -07001267 if src is None:
1268 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1269 else:
1270 _, self.device = GetTypeAndDevice("/" + partition,
1271 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001272
1273 def WriteScript(self, script, output_zip, progress=None):
1274 if not self.src:
1275 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001276 script.Print("Patching %s image unconditionally..." % (self.partition,))
1277 else:
1278 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001279
Dan Albert8b72aef2015-03-23 19:13:21 -07001280 if progress:
1281 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001282 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001283 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001284
Tao Bao9bc6bb22015-11-09 16:58:28 -08001285 def WriteStrictVerifyScript(self, script):
1286 """Verify all the blocks in the care_map, including clobbered blocks.
1287
1288 This differs from the WriteVerifyScript() function: a) it prints different
1289 error messages; b) it doesn't allow half-way updated images to pass the
1290 verification."""
1291
1292 partition = self.partition
1293 script.Print("Verifying %s..." % (partition,))
1294 ranges = self.tgt.care_map
1295 ranges_str = ranges.to_string_raw()
1296 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1297 'ui_print(" Verified.") || '
1298 'ui_print("\\"%s\\" has unexpected contents.");' % (
1299 self.device, ranges_str,
1300 self.tgt.TotalSha1(include_clobbered_blocks=True),
1301 self.device))
1302 script.AppendExtra("")
1303
Jesse Zhao75bcea02015-01-06 10:59:53 -08001304 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001305 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001306 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001307 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001308 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001309 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1310 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001311 if self.version >= 4:
1312 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1313 'block_image_verify("%s", '
1314 'package_extract_file("%s.transfer.list"), '
1315 '"%s.new.dat", "%s.patch.dat") || '
1316 '(block_image_recover("%s", "%s") && '
1317 'block_image_verify("%s", '
1318 'package_extract_file("%s.transfer.list"), '
1319 '"%s.new.dat", "%s.patch.dat"))) then') % (
1320 self.device, ranges_str, self.src.TotalSha1(),
1321 self.device, partition, partition, partition,
1322 self.device, ranges_str,
1323 self.device, partition, partition, partition))
1324 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001325 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1326 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001327 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001328 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001329 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001330 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001331 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001332 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001333 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001334 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001335 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001336
Tao Baodd2a5892015-03-12 12:32:37 -07001337 # When generating incrementals for the system and vendor partitions,
1338 # explicitly check the first block (which contains the superblock) of
1339 # the partition to see if it's what we expect. If this check fails,
1340 # give an explicit log message about the partition having been
1341 # remounted R/W (the most likely explanation) and the need to flash to
1342 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001343 if self.check_first_block:
1344 self._CheckFirstBlock(script)
1345
Tao Baodd2a5892015-03-12 12:32:37 -07001346 # Abort the OTA update. Note that the incremental OTA cannot be applied
1347 # even if it may match the checksum of the target partition.
1348 # a) If version < 3, operations like move and erase will make changes
1349 # unconditionally and damage the partition.
1350 # b) If version >= 3, it won't even reach here.
1351 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1352 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001353
Tao Bao5fcaaef2015-06-01 13:40:49 -07001354 def _WritePostInstallVerifyScript(self, script):
1355 partition = self.partition
1356 script.Print('Verifying the updated %s image...' % (partition,))
1357 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1358 ranges = self.tgt.care_map
1359 ranges_str = ranges.to_string_raw()
1360 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1361 self.device, ranges_str,
1362 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001363
1364 # Bug: 20881595
1365 # Verify that extended blocks are really zeroed out.
1366 if self.tgt.extended:
1367 ranges_str = self.tgt.extended.to_string_raw()
1368 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1369 self.device, ranges_str,
1370 self._HashZeroBlocks(self.tgt.extended.size())))
1371 script.Print('Verified the updated %s image.' % (partition,))
1372 script.AppendExtra(
1373 'else\n'
1374 ' abort("%s partition has unexpected non-zero contents after OTA '
1375 'update");\n'
1376 'endif;' % (partition,))
1377 else:
1378 script.Print('Verified the updated %s image.' % (partition,))
1379
Tao Bao5fcaaef2015-06-01 13:40:49 -07001380 script.AppendExtra(
1381 'else\n'
1382 ' abort("%s partition has unexpected contents after OTA update");\n'
1383 'endif;' % (partition,))
1384
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001385 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001386 ZipWrite(output_zip,
1387 '{}.transfer.list'.format(self.path),
1388 '{}.transfer.list'.format(self.partition))
1389 ZipWrite(output_zip,
1390 '{}.new.dat'.format(self.path),
1391 '{}.new.dat'.format(self.partition))
1392 ZipWrite(output_zip,
1393 '{}.patch.dat'.format(self.path),
1394 '{}.patch.dat'.format(self.partition),
1395 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001396
Dan Albert8e0178d2015-01-27 15:53:15 -08001397 call = ('block_image_update("{device}", '
1398 'package_extract_file("{partition}.transfer.list"), '
1399 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1400 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001401 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001402
Dan Albert8b72aef2015-03-23 19:13:21 -07001403 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001404 data = source.ReadRangeSet(ranges)
1405 ctx = sha1()
1406
1407 for p in data:
1408 ctx.update(p)
1409
1410 return ctx.hexdigest()
1411
Tao Baoe9b61912015-07-09 17:37:49 -07001412 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1413 """Return the hash value for all zero blocks."""
1414 zero_block = '\x00' * 4096
1415 ctx = sha1()
1416 for _ in range(num_blocks):
1417 ctx.update(zero_block)
1418
1419 return ctx.hexdigest()
1420
Tao Bao5ece99d2015-05-12 11:42:31 -07001421 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1422 # remounting R/W. Will change the checking to a finer-grained way to
1423 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001424 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001425 r = rangelib.RangeSet((0, 1))
1426 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001427
1428 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1429 'abort("%s has been remounted R/W; '
1430 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001431 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001432 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001433
1434DataImage = blockimgdiff.DataImage
1435
1436
Doug Zongker96a57e72010-09-26 14:57:41 -07001437# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001438PARTITION_TYPES = {
1439 "yaffs2": "MTD",
1440 "mtd": "MTD",
1441 "ext4": "EMMC",
1442 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001443 "f2fs": "EMMC",
1444 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001445}
Doug Zongker96a57e72010-09-26 14:57:41 -07001446
1447def GetTypeAndDevice(mount_point, info):
1448 fstab = info["fstab"]
1449 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001450 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1451 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001452 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001453 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001454
1455
1456def ParseCertificate(data):
1457 """Parse a PEM-format certificate."""
1458 cert = []
1459 save = False
1460 for line in data.split("\n"):
1461 if "--END CERTIFICATE--" in line:
1462 break
1463 if save:
1464 cert.append(line)
1465 if "--BEGIN CERTIFICATE--" in line:
1466 save = True
1467 cert = "".join(cert).decode('base64')
1468 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001469
Doug Zongker412c02f2014-02-13 10:58:24 -08001470def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1471 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001472 """Generate a binary patch that creates the recovery image starting
1473 with the boot image. (Most of the space in these images is just the
1474 kernel, which is identical for the two, so the resulting patch
1475 should be efficient.) Add it to the output zip, along with a shell
1476 script that is run from init.rc on first boot to actually do the
1477 patching and install the new recovery image.
1478
1479 recovery_img and boot_img should be File objects for the
1480 corresponding images. info should be the dictionary returned by
1481 common.LoadInfoDict() on the input target_files.
1482 """
1483
Doug Zongker412c02f2014-02-13 10:58:24 -08001484 if info_dict is None:
1485 info_dict = OPTIONS.info_dict
1486
Tao Baof2cffbd2015-07-22 12:33:18 -07001487 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001488 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001489
Tao Baof2cffbd2015-07-22 12:33:18 -07001490 if full_recovery_image:
1491 output_sink("etc/recovery.img", recovery_img.data)
1492
1493 else:
1494 diff_program = ["imgdiff"]
1495 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1496 if os.path.exists(path):
1497 diff_program.append("-b")
1498 diff_program.append(path)
1499 bonus_args = "-b /system/etc/recovery-resource.dat"
1500 else:
1501 bonus_args = ""
1502
1503 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1504 _, _, patch = d.ComputePatch()
1505 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001506
Dan Albertebb19aa2015-03-27 19:11:53 -07001507 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001508 # The following GetTypeAndDevice()s need to use the path in the target
1509 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001510 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1511 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1512 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001513 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001514
Tao Baof2cffbd2015-07-22 12:33:18 -07001515 if full_recovery_image:
1516 sh = """#!/system/bin/sh
1517if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1518 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"
1519else
1520 log -t recovery "Recovery image already installed"
1521fi
1522""" % {'type': recovery_type,
1523 'device': recovery_device,
1524 'sha1': recovery_img.sha1,
1525 'size': recovery_img.size}
1526 else:
1527 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001528if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1529 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"
1530else
1531 log -t recovery "Recovery image already installed"
1532fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001533""" % {'boot_size': boot_img.size,
1534 'boot_sha1': boot_img.sha1,
1535 'recovery_size': recovery_img.size,
1536 'recovery_sha1': recovery_img.sha1,
1537 'boot_type': boot_type,
1538 'boot_device': boot_device,
1539 'recovery_type': recovery_type,
1540 'recovery_device': recovery_device,
1541 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001542
1543 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001544 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001545 # target-files expects it to be, and put it there.
1546 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001547 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001548 if system_root_image:
1549 init_rc_dir = os.path.join(input_dir, "ROOT")
1550 else:
1551 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001552 init_rc_files = os.listdir(init_rc_dir)
1553 for init_rc_file in init_rc_files:
1554 if (not init_rc_file.startswith('init.') or
1555 not init_rc_file.endswith('.rc')):
1556 continue
1557
1558 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001559 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001560 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001561 if m:
1562 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001563 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001564 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001565
1566 if found:
1567 break
1568
1569 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001570
1571 output_sink(sh_location, sh)