blob: cad654a71b08c6a3404687de88acd041daecf71e [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",
447 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
448 img.name]
449 p = Run(cmd, stdout=subprocess.PIPE)
450 p.communicate()
451 assert p.returncode == 0, "vboot_signer of %s image failed" % path
452
Tao Baof3282b42015-04-01 11:21:55 -0700453 # Clean up the temp files.
454 img_unsigned.close()
455 img_keyblock.close()
456
Doug Zongkereef39442009-04-02 12:14:19 -0700457 img.seek(os.SEEK_SET, 0)
458 data = img.read()
459
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700460 if has_ramdisk:
461 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700462 img.close()
463
464 return data
465
466
Doug Zongkerd5131602012-08-02 14:46:42 -0700467def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
468 info_dict=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700469 """Return a File object with the desired bootable image.
470
471 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
472 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
473 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700474
Doug Zongker55d93282011-01-25 17:03:34 -0800475 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
476 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700477 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800478 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700479
480 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
481 if os.path.exists(prebuilt_path):
482 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
483 return File.FromLocalFile(name, prebuilt_path)
484
485 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700486
487 if info_dict is None:
488 info_dict = OPTIONS.info_dict
489
490 # With system_root_image == "true", we don't pack ramdisk into the boot image.
491 has_ramdisk = (info_dict.get("system_root_image", None) != "true" or
492 prebuilt_name != "boot.img")
493
Doug Zongker6f1d0312014-08-22 08:07:12 -0700494 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700495 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
496 os.path.join(unpack_dir, fs_config),
497 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700498 if data:
499 return File(name, data)
500 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800501
Doug Zongkereef39442009-04-02 12:14:19 -0700502
Doug Zongker75f17362009-12-08 13:46:44 -0800503def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800504 """Unzip the given archive into a temporary directory and return the name.
505
506 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
507 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
508
509 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
510 main file), open for reading.
511 """
Doug Zongkereef39442009-04-02 12:14:19 -0700512
513 tmp = tempfile.mkdtemp(prefix="targetfiles-")
514 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800515
516 def unzip_to_dir(filename, dirname):
517 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
518 if pattern is not None:
519 cmd.append(pattern)
520 p = Run(cmd, stdout=subprocess.PIPE)
521 p.communicate()
522 if p.returncode != 0:
523 raise ExternalError("failed to unzip input target-files \"%s\"" %
524 (filename,))
525
526 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
527 if m:
528 unzip_to_dir(m.group(1), tmp)
529 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
530 filename = m.group(1)
531 else:
532 unzip_to_dir(filename, tmp)
533
534 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700535
536
537def GetKeyPasswords(keylist):
538 """Given a list of keys, prompt the user to enter passwords for
539 those which require them. Return a {key: password} dict. password
540 will be None if the key has no password."""
541
Doug Zongker8ce7c252009-05-22 13:34:54 -0700542 no_passwords = []
543 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700544 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700545 devnull = open("/dev/null", "w+b")
546 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800547 # We don't need a password for things that aren't really keys.
548 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700549 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700550 continue
551
T.R. Fullhart37e10522013-03-18 10:31:26 -0700552 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700553 "-inform", "DER", "-nocrypt"],
554 stdin=devnull.fileno(),
555 stdout=devnull.fileno(),
556 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700557 p.communicate()
558 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700559 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700560 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700561 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700562 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
563 "-inform", "DER", "-passin", "pass:"],
564 stdin=devnull.fileno(),
565 stdout=devnull.fileno(),
566 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700567 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700568 if p.returncode == 0:
569 # Encrypted key with empty string as password.
570 key_passwords[k] = ''
571 elif stderr.startswith('Error decrypting key'):
572 # Definitely encrypted key.
573 # It would have said "Error reading key" if it didn't parse correctly.
574 need_passwords.append(k)
575 else:
576 # Potentially, a type of key that openssl doesn't understand.
577 # We'll let the routines in signapk.jar handle it.
578 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700579 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700580
T.R. Fullhart37e10522013-03-18 10:31:26 -0700581 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700582 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700583 return key_passwords
584
585
Doug Zongker951495f2009-08-14 12:44:19 -0700586def SignFile(input_name, output_name, key, password, align=None,
587 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700588 """Sign the input_name zip/jar/apk, producing output_name. Use the
589 given key and password (the latter may be None if the key does not
590 have a password.
591
592 If align is an integer > 1, zipalign is run to align stored files in
593 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700594
595 If whole_file is true, use the "-w" option to SignApk to embed a
596 signature that covers the whole file in the archive comment of the
597 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700598 """
Doug Zongker951495f2009-08-14 12:44:19 -0700599
Doug Zongkereef39442009-04-02 12:14:19 -0700600 if align == 0 or align == 1:
601 align = None
602
603 if align:
604 temp = tempfile.NamedTemporaryFile()
605 sign_name = temp.name
606 else:
607 sign_name = output_name
608
Baligh Uddin339ee492014-09-05 11:18:07 -0700609 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700610 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
611 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700612 if whole_file:
613 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700614 cmd.extend([key + OPTIONS.public_key_suffix,
615 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700616 input_name, sign_name])
617
618 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700619 if password is not None:
620 password += "\n"
621 p.communicate(password)
622 if p.returncode != 0:
623 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
624
625 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700626 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700627 p.communicate()
628 if p.returncode != 0:
629 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
630 temp.close()
631
632
Doug Zongker37974732010-09-16 17:44:38 -0700633def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700634 """Check the data string passed against the max size limit, if
635 any, for the given target. Raise exception if the data is too big.
636 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700637
Dan Albert8b72aef2015-03-23 19:13:21 -0700638 if target.endswith(".img"):
639 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700640 mount_point = "/" + target
641
Ying Wangf8824af2014-06-03 14:07:27 -0700642 fs_type = None
643 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700644 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700645 if mount_point == "/userdata":
646 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700647 p = info_dict["fstab"][mount_point]
648 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800649 device = p.device
650 if "/" in device:
651 device = device[device.rfind("/")+1:]
652 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700653 if not fs_type or not limit:
654 return
Doug Zongkereef39442009-04-02 12:14:19 -0700655
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700656 if fs_type == "yaffs2":
657 # image size should be increased by 1/64th to account for the
658 # spare area (64 bytes per 2k page)
659 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800660 size = len(data)
661 pct = float(size) * 100.0 / limit
662 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
663 if pct >= 99.0:
664 raise ExternalError(msg)
665 elif pct >= 95.0:
666 print
667 print " WARNING: ", msg
668 print
669 elif OPTIONS.verbose:
670 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700671
672
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800673def ReadApkCerts(tf_zip):
674 """Given a target_files ZipFile, parse the META/apkcerts.txt file
675 and return a {package: cert} dict."""
676 certmap = {}
677 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
678 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700679 if not line:
680 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800681 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
682 r'private_key="(.*)"$', line)
683 if m:
684 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700685 public_key_suffix_len = len(OPTIONS.public_key_suffix)
686 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800687 if cert in SPECIAL_CERT_STRINGS and not privkey:
688 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700689 elif (cert.endswith(OPTIONS.public_key_suffix) and
690 privkey.endswith(OPTIONS.private_key_suffix) and
691 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
692 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800693 else:
694 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
695 return certmap
696
697
Doug Zongkereef39442009-04-02 12:14:19 -0700698COMMON_DOCSTRING = """
699 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700700 Prepend <dir>/bin to the list of places to search for binaries
701 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700702
Doug Zongker05d3dea2009-06-22 11:32:31 -0700703 -s (--device_specific) <file>
704 Path to the python module containing device-specific
705 releasetools code.
706
Doug Zongker8bec09e2009-11-30 15:37:14 -0800707 -x (--extra) <key=value>
708 Add a key/value pair to the 'extras' dict, which device-specific
709 extension code may look at.
710
Doug Zongkereef39442009-04-02 12:14:19 -0700711 -v (--verbose)
712 Show command lines being executed.
713
714 -h (--help)
715 Display this usage message and exit.
716"""
717
718def Usage(docstring):
719 print docstring.rstrip("\n")
720 print COMMON_DOCSTRING
721
722
723def ParseOptions(argv,
724 docstring,
725 extra_opts="", extra_long_opts=(),
726 extra_option_handler=None):
727 """Parse the options in argv and return any arguments that aren't
728 flags. docstring is the calling module's docstring, to be displayed
729 for errors and -h. extra_opts and extra_long_opts are for flags
730 defined by the caller, which are processed by passing them to
731 extra_option_handler."""
732
733 try:
734 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800735 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700736 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700737 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700738 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
739 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800740 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700741 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700742 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700743 Usage(docstring)
744 print "**", str(err), "**"
745 sys.exit(2)
746
Doug Zongkereef39442009-04-02 12:14:19 -0700747 for o, a in opts:
748 if o in ("-h", "--help"):
749 Usage(docstring)
750 sys.exit()
751 elif o in ("-v", "--verbose"):
752 OPTIONS.verbose = True
753 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700754 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700755 elif o in ("--signapk_path",):
756 OPTIONS.signapk_path = a
757 elif o in ("--extra_signapk_args",):
758 OPTIONS.extra_signapk_args = shlex.split(a)
759 elif o in ("--java_path",):
760 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700761 elif o in ("--java_args",):
762 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700763 elif o in ("--public_key_suffix",):
764 OPTIONS.public_key_suffix = a
765 elif o in ("--private_key_suffix",):
766 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800767 elif o in ("--boot_signer_path",):
768 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700769 elif o in ("--boot_signer_args",):
770 OPTIONS.boot_signer_args = shlex.split(a)
771 elif o in ("--verity_signer_path",):
772 OPTIONS.verity_signer_path = a
773 elif o in ("--verity_signer_args",):
774 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700775 elif o in ("-s", "--device_specific"):
776 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800777 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800778 key, value = a.split("=", 1)
779 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700780 else:
781 if extra_option_handler is None or not extra_option_handler(o, a):
782 assert False, "unknown option \"%s\"" % (o,)
783
Doug Zongker85448772014-09-09 14:59:20 -0700784 if OPTIONS.search_path:
785 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
786 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700787
788 return args
789
790
Doug Zongkerfc44a512014-08-26 13:10:25 -0700791def MakeTempFile(prefix=None, suffix=None):
792 """Make a temp file and add it to the list of things to be deleted
793 when Cleanup() is called. Return the filename."""
794 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
795 os.close(fd)
796 OPTIONS.tempfiles.append(fn)
797 return fn
798
799
Doug Zongkereef39442009-04-02 12:14:19 -0700800def Cleanup():
801 for i in OPTIONS.tempfiles:
802 if os.path.isdir(i):
803 shutil.rmtree(i)
804 else:
805 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700806
807
808class PasswordManager(object):
809 def __init__(self):
810 self.editor = os.getenv("EDITOR", None)
811 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
812
813 def GetPasswords(self, items):
814 """Get passwords corresponding to each string in 'items',
815 returning a dict. (The dict may have keys in addition to the
816 values in 'items'.)
817
818 Uses the passwords in $ANDROID_PW_FILE if available, letting the
819 user edit that file to add more needed passwords. If no editor is
820 available, or $ANDROID_PW_FILE isn't define, prompts the user
821 interactively in the ordinary way.
822 """
823
824 current = self.ReadFile()
825
826 first = True
827 while True:
828 missing = []
829 for i in items:
830 if i not in current or not current[i]:
831 missing.append(i)
832 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700833 if not missing:
834 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700835
836 for i in missing:
837 current[i] = ""
838
839 if not first:
840 print "key file %s still missing some passwords." % (self.pwfile,)
841 answer = raw_input("try to edit again? [y]> ").strip()
842 if answer and answer[0] not in 'yY':
843 raise RuntimeError("key passwords unavailable")
844 first = False
845
846 current = self.UpdateAndReadFile(current)
847
Dan Albert8b72aef2015-03-23 19:13:21 -0700848 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700849 """Prompt the user to enter a value (password) for each key in
850 'current' whose value is fales. Returns a new dict with all the
851 values.
852 """
853 result = {}
854 for k, v in sorted(current.iteritems()):
855 if v:
856 result[k] = v
857 else:
858 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700859 result[k] = getpass.getpass(
860 "Enter password for %s key> " % k).strip()
861 if result[k]:
862 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700863 return result
864
865 def UpdateAndReadFile(self, current):
866 if not self.editor or not self.pwfile:
867 return self.PromptResult(current)
868
869 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700870 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700871 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
872 f.write("# (Additional spaces are harmless.)\n\n")
873
874 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700875 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
876 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700877 f.write("[[[ %s ]]] %s\n" % (v, k))
878 if not v and first_line is None:
879 # position cursor on first line with no password.
880 first_line = i + 4
881 f.close()
882
883 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
884 _, _ = p.communicate()
885
886 return self.ReadFile()
887
888 def ReadFile(self):
889 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700890 if self.pwfile is None:
891 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700892 try:
893 f = open(self.pwfile, "r")
894 for line in f:
895 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700896 if not line or line[0] == '#':
897 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700898 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
899 if not m:
900 print "failed to parse password file: ", line
901 else:
902 result[m.group(2)] = m.group(1)
903 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700904 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700905 if e.errno != errno.ENOENT:
906 print "error reading password file: ", str(e)
907 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700908
909
Dan Albert8e0178d2015-01-27 15:53:15 -0800910def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
911 compress_type=None):
912 import datetime
913
914 # http://b/18015246
915 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
916 # for files larger than 2GiB. We can work around this by adjusting their
917 # limit. Note that `zipfile.writestr()` will not work for strings larger than
918 # 2GiB. The Python interpreter sometimes rejects strings that large (though
919 # it isn't clear to me exactly what circumstances cause this).
920 # `zipfile.write()` must be used directly to work around this.
921 #
922 # This mess can be avoided if we port to python3.
923 saved_zip64_limit = zipfile.ZIP64_LIMIT
924 zipfile.ZIP64_LIMIT = (1 << 32) - 1
925
926 if compress_type is None:
927 compress_type = zip_file.compression
928 if arcname is None:
929 arcname = filename
930
931 saved_stat = os.stat(filename)
932
933 try:
934 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
935 # file to be zipped and reset it when we're done.
936 os.chmod(filename, perms)
937
938 # Use a fixed timestamp so the output is repeatable.
939 epoch = datetime.datetime.fromtimestamp(0)
940 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
941 os.utime(filename, (timestamp, timestamp))
942
943 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
944 finally:
945 os.chmod(filename, saved_stat.st_mode)
946 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
947 zipfile.ZIP64_LIMIT = saved_zip64_limit
948
949
Tao Bao58c1b962015-05-20 09:32:18 -0700950def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700951 compress_type=None):
952 """Wrap zipfile.writestr() function to work around the zip64 limit.
953
954 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
955 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
956 when calling crc32(bytes).
957
958 But it still works fine to write a shorter string into a large zip file.
959 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
960 when we know the string won't be too long.
961 """
962
963 saved_zip64_limit = zipfile.ZIP64_LIMIT
964 zipfile.ZIP64_LIMIT = (1 << 32) - 1
965
966 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
967 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700968 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700969 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700970 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800971 else:
Tao Baof3282b42015-04-01 11:21:55 -0700972 zinfo = zinfo_or_arcname
973
974 # If compress_type is given, it overrides the value in zinfo.
975 if compress_type is not None:
976 zinfo.compress_type = compress_type
977
Tao Bao58c1b962015-05-20 09:32:18 -0700978 # If perms is given, it has a priority.
979 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700980 # If perms doesn't set the file type, mark it as a regular file.
981 if perms & 0o770000 == 0:
982 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -0700983 zinfo.external_attr = perms << 16
984
Tao Baof3282b42015-04-01 11:21:55 -0700985 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700986 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
987
Dan Albert8b72aef2015-03-23 19:13:21 -0700988 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700989 zipfile.ZIP64_LIMIT = saved_zip64_limit
990
991
992def ZipClose(zip_file):
993 # http://b/18015246
994 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
995 # central directory.
996 saved_zip64_limit = zipfile.ZIP64_LIMIT
997 zipfile.ZIP64_LIMIT = (1 << 32) - 1
998
999 zip_file.close()
1000
1001 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001002
1003
1004class DeviceSpecificParams(object):
1005 module = None
1006 def __init__(self, **kwargs):
1007 """Keyword arguments to the constructor become attributes of this
1008 object, which is passed to all functions in the device-specific
1009 module."""
1010 for k, v in kwargs.iteritems():
1011 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001012 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001013
1014 if self.module is None:
1015 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001016 if not path:
1017 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001018 try:
1019 if os.path.isdir(path):
1020 info = imp.find_module("releasetools", [path])
1021 else:
1022 d, f = os.path.split(path)
1023 b, x = os.path.splitext(f)
1024 if x == ".py":
1025 f = b
1026 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001027 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001028 self.module = imp.load_module("device_specific", *info)
1029 except ImportError:
1030 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001031
1032 def _DoCall(self, function_name, *args, **kwargs):
1033 """Call the named function in the device-specific module, passing
1034 the given args and kwargs. The first argument to the call will be
1035 the DeviceSpecific object itself. If there is no module, or the
1036 module does not define the function, return the value of the
1037 'default' kwarg (which itself defaults to None)."""
1038 if self.module is None or not hasattr(self.module, function_name):
1039 return kwargs.get("default", None)
1040 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1041
1042 def FullOTA_Assertions(self):
1043 """Called after emitting the block of assertions at the top of a
1044 full OTA package. Implementations can add whatever additional
1045 assertions they like."""
1046 return self._DoCall("FullOTA_Assertions")
1047
Doug Zongkere5ff5902012-01-17 10:55:37 -08001048 def FullOTA_InstallBegin(self):
1049 """Called at the start of full OTA installation."""
1050 return self._DoCall("FullOTA_InstallBegin")
1051
Doug Zongker05d3dea2009-06-22 11:32:31 -07001052 def FullOTA_InstallEnd(self):
1053 """Called at the end of full OTA installation; typically this is
1054 used to install the image for the device's baseband processor."""
1055 return self._DoCall("FullOTA_InstallEnd")
1056
1057 def IncrementalOTA_Assertions(self):
1058 """Called after emitting the block of assertions at the top of an
1059 incremental OTA package. Implementations can add whatever
1060 additional assertions they like."""
1061 return self._DoCall("IncrementalOTA_Assertions")
1062
Doug Zongkere5ff5902012-01-17 10:55:37 -08001063 def IncrementalOTA_VerifyBegin(self):
1064 """Called at the start of the verification phase of incremental
1065 OTA installation; additional checks can be placed here to abort
1066 the script before any changes are made."""
1067 return self._DoCall("IncrementalOTA_VerifyBegin")
1068
Doug Zongker05d3dea2009-06-22 11:32:31 -07001069 def IncrementalOTA_VerifyEnd(self):
1070 """Called at the end of the verification phase of incremental OTA
1071 installation; additional checks can be placed here to abort the
1072 script before any changes are made."""
1073 return self._DoCall("IncrementalOTA_VerifyEnd")
1074
Doug Zongkere5ff5902012-01-17 10:55:37 -08001075 def IncrementalOTA_InstallBegin(self):
1076 """Called at the start of incremental OTA installation (after
1077 verification is complete)."""
1078 return self._DoCall("IncrementalOTA_InstallBegin")
1079
Doug Zongker05d3dea2009-06-22 11:32:31 -07001080 def IncrementalOTA_InstallEnd(self):
1081 """Called at the end of incremental OTA installation; typically
1082 this is used to install the image for the device's baseband
1083 processor."""
1084 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001085
1086class File(object):
1087 def __init__(self, name, data):
1088 self.name = name
1089 self.data = data
1090 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001091 self.sha1 = sha1(data).hexdigest()
1092
1093 @classmethod
1094 def FromLocalFile(cls, name, diskname):
1095 f = open(diskname, "rb")
1096 data = f.read()
1097 f.close()
1098 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001099
1100 def WriteToTemp(self):
1101 t = tempfile.NamedTemporaryFile()
1102 t.write(self.data)
1103 t.flush()
1104 return t
1105
Geremy Condra36bd3652014-02-06 19:45:10 -08001106 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001107 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001108
1109DIFF_PROGRAM_BY_EXT = {
1110 ".gz" : "imgdiff",
1111 ".zip" : ["imgdiff", "-z"],
1112 ".jar" : ["imgdiff", "-z"],
1113 ".apk" : ["imgdiff", "-z"],
1114 ".img" : "imgdiff",
1115 }
1116
1117class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001118 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001119 self.tf = tf
1120 self.sf = sf
1121 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001122 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001123
1124 def ComputePatch(self):
1125 """Compute the patch (as a string of data) needed to turn sf into
1126 tf. Returns the same tuple as GetPatch()."""
1127
1128 tf = self.tf
1129 sf = self.sf
1130
Doug Zongker24cd2802012-08-14 16:36:15 -07001131 if self.diff_program:
1132 diff_program = self.diff_program
1133 else:
1134 ext = os.path.splitext(tf.name)[1]
1135 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001136
1137 ttemp = tf.WriteToTemp()
1138 stemp = sf.WriteToTemp()
1139
1140 ext = os.path.splitext(tf.name)[1]
1141
1142 try:
1143 ptemp = tempfile.NamedTemporaryFile()
1144 if isinstance(diff_program, list):
1145 cmd = copy.copy(diff_program)
1146 else:
1147 cmd = [diff_program]
1148 cmd.append(stemp.name)
1149 cmd.append(ttemp.name)
1150 cmd.append(ptemp.name)
1151 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001152 err = []
1153 def run():
1154 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001155 if e:
1156 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001157 th = threading.Thread(target=run)
1158 th.start()
1159 th.join(timeout=300) # 5 mins
1160 if th.is_alive():
1161 print "WARNING: diff command timed out"
1162 p.terminate()
1163 th.join(5)
1164 if th.is_alive():
1165 p.kill()
1166 th.join()
1167
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001168 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001169 print "WARNING: failure running %s:\n%s\n" % (
1170 diff_program, "".join(err))
1171 self.patch = None
1172 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001173 diff = ptemp.read()
1174 finally:
1175 ptemp.close()
1176 stemp.close()
1177 ttemp.close()
1178
1179 self.patch = diff
1180 return self.tf, self.sf, self.patch
1181
1182
1183 def GetPatch(self):
1184 """Return a tuple (target_file, source_file, patch_data).
1185 patch_data may be None if ComputePatch hasn't been called, or if
1186 computing the patch failed."""
1187 return self.tf, self.sf, self.patch
1188
1189
1190def ComputeDifferences(diffs):
1191 """Call ComputePatch on all the Difference objects in 'diffs'."""
1192 print len(diffs), "diffs to compute"
1193
1194 # Do the largest files first, to try and reduce the long-pole effect.
1195 by_size = [(i.tf.size, i) for i in diffs]
1196 by_size.sort(reverse=True)
1197 by_size = [i[1] for i in by_size]
1198
1199 lock = threading.Lock()
1200 diff_iter = iter(by_size) # accessed under lock
1201
1202 def worker():
1203 try:
1204 lock.acquire()
1205 for d in diff_iter:
1206 lock.release()
1207 start = time.time()
1208 d.ComputePatch()
1209 dur = time.time() - start
1210 lock.acquire()
1211
1212 tf, sf, patch = d.GetPatch()
1213 if sf.name == tf.name:
1214 name = tf.name
1215 else:
1216 name = "%s (%s)" % (tf.name, sf.name)
1217 if patch is None:
1218 print "patching failed! %s" % (name,)
1219 else:
1220 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1221 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1222 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001223 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001224 print e
1225 raise
1226
1227 # start worker threads; wait for them all to finish.
1228 threads = [threading.Thread(target=worker)
1229 for i in range(OPTIONS.worker_threads)]
1230 for th in threads:
1231 th.start()
1232 while threads:
1233 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001234
1235
Dan Albert8b72aef2015-03-23 19:13:21 -07001236class BlockDifference(object):
1237 def __init__(self, partition, tgt, src=None, check_first_block=False,
1238 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001239 self.tgt = tgt
1240 self.src = src
1241 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001242 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001243
Tao Bao5ece99d2015-05-12 11:42:31 -07001244 # Due to http://b/20939131, check_first_block is disabled temporarily.
1245 assert not self.check_first_block
1246
Tao Baodd2a5892015-03-12 12:32:37 -07001247 if version is None:
1248 version = 1
1249 if OPTIONS.info_dict:
1250 version = max(
1251 int(i) for i in
1252 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1253 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001254
1255 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001256 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001257 tmpdir = tempfile.mkdtemp()
1258 OPTIONS.tempfiles.append(tmpdir)
1259 self.path = os.path.join(tmpdir, partition)
1260 b.Compute(self.path)
1261
Tao Baoaac4ad52015-10-16 15:26:34 -07001262 if src is None:
1263 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1264 else:
1265 _, self.device = GetTypeAndDevice("/" + partition,
1266 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001267
1268 def WriteScript(self, script, output_zip, progress=None):
1269 if not self.src:
1270 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001271 script.Print("Patching %s image unconditionally..." % (self.partition,))
1272 else:
1273 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001274
Dan Albert8b72aef2015-03-23 19:13:21 -07001275 if progress:
1276 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001277 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001278 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001279
1280 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001281 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001282 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001283 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001284 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001285 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1286 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001287 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001288 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1289 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001290 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001291 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001292 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001293 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001294 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001295 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001296 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001297 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001298 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001299
Tao Baodd2a5892015-03-12 12:32:37 -07001300 # When generating incrementals for the system and vendor partitions,
1301 # explicitly check the first block (which contains the superblock) of
1302 # the partition to see if it's what we expect. If this check fails,
1303 # give an explicit log message about the partition having been
1304 # remounted R/W (the most likely explanation) and the need to flash to
1305 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001306 if self.check_first_block:
1307 self._CheckFirstBlock(script)
1308
Tao Baodd2a5892015-03-12 12:32:37 -07001309 # Abort the OTA update. Note that the incremental OTA cannot be applied
1310 # even if it may match the checksum of the target partition.
1311 # a) If version < 3, operations like move and erase will make changes
1312 # unconditionally and damage the partition.
1313 # b) If version >= 3, it won't even reach here.
1314 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1315 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001316
Tao Bao5fcaaef2015-06-01 13:40:49 -07001317 def _WritePostInstallVerifyScript(self, script):
1318 partition = self.partition
1319 script.Print('Verifying the updated %s image...' % (partition,))
1320 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1321 ranges = self.tgt.care_map
1322 ranges_str = ranges.to_string_raw()
1323 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1324 self.device, ranges_str,
1325 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001326
1327 # Bug: 20881595
1328 # Verify that extended blocks are really zeroed out.
1329 if self.tgt.extended:
1330 ranges_str = self.tgt.extended.to_string_raw()
1331 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1332 self.device, ranges_str,
1333 self._HashZeroBlocks(self.tgt.extended.size())))
1334 script.Print('Verified the updated %s image.' % (partition,))
1335 script.AppendExtra(
1336 'else\n'
1337 ' abort("%s partition has unexpected non-zero contents after OTA '
1338 'update");\n'
1339 'endif;' % (partition,))
1340 else:
1341 script.Print('Verified the updated %s image.' % (partition,))
1342
Tao Bao5fcaaef2015-06-01 13:40:49 -07001343 script.AppendExtra(
1344 'else\n'
1345 ' abort("%s partition has unexpected contents after OTA update");\n'
1346 'endif;' % (partition,))
1347
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001348 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001349 ZipWrite(output_zip,
1350 '{}.transfer.list'.format(self.path),
1351 '{}.transfer.list'.format(self.partition))
1352 ZipWrite(output_zip,
1353 '{}.new.dat'.format(self.path),
1354 '{}.new.dat'.format(self.partition))
1355 ZipWrite(output_zip,
1356 '{}.patch.dat'.format(self.path),
1357 '{}.patch.dat'.format(self.partition),
1358 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001359
Dan Albert8e0178d2015-01-27 15:53:15 -08001360 call = ('block_image_update("{device}", '
1361 'package_extract_file("{partition}.transfer.list"), '
1362 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1363 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001364 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001365
Dan Albert8b72aef2015-03-23 19:13:21 -07001366 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001367 data = source.ReadRangeSet(ranges)
1368 ctx = sha1()
1369
1370 for p in data:
1371 ctx.update(p)
1372
1373 return ctx.hexdigest()
1374
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001375 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1376 """Return the hash value for all zero blocks."""
1377 zero_block = '\x00' * 4096
1378 ctx = sha1()
1379 for _ in range(num_blocks):
1380 ctx.update(zero_block)
1381
1382 return ctx.hexdigest()
1383
Tao Bao5ece99d2015-05-12 11:42:31 -07001384 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1385 # remounting R/W. Will change the checking to a finer-grained way to
1386 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001387 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001388 r = rangelib.RangeSet((0, 1))
1389 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001390
1391 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1392 'abort("%s has been remounted R/W; '
1393 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001394 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001395 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001396
1397DataImage = blockimgdiff.DataImage
1398
1399
Doug Zongker96a57e72010-09-26 14:57:41 -07001400# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001401PARTITION_TYPES = {
1402 "yaffs2": "MTD",
1403 "mtd": "MTD",
1404 "ext4": "EMMC",
1405 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001406 "f2fs": "EMMC",
1407 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001408}
Doug Zongker96a57e72010-09-26 14:57:41 -07001409
1410def GetTypeAndDevice(mount_point, info):
1411 fstab = info["fstab"]
1412 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001413 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1414 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001415 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001416 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001417
1418
1419def ParseCertificate(data):
1420 """Parse a PEM-format certificate."""
1421 cert = []
1422 save = False
1423 for line in data.split("\n"):
1424 if "--END CERTIFICATE--" in line:
1425 break
1426 if save:
1427 cert.append(line)
1428 if "--BEGIN CERTIFICATE--" in line:
1429 save = True
1430 cert = "".join(cert).decode('base64')
1431 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001432
Doug Zongker412c02f2014-02-13 10:58:24 -08001433def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1434 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001435 """Generate a binary patch that creates the recovery image starting
1436 with the boot image. (Most of the space in these images is just the
1437 kernel, which is identical for the two, so the resulting patch
1438 should be efficient.) Add it to the output zip, along with a shell
1439 script that is run from init.rc on first boot to actually do the
1440 patching and install the new recovery image.
1441
1442 recovery_img and boot_img should be File objects for the
1443 corresponding images. info should be the dictionary returned by
1444 common.LoadInfoDict() on the input target_files.
1445 """
1446
Doug Zongker412c02f2014-02-13 10:58:24 -08001447 if info_dict is None:
1448 info_dict = OPTIONS.info_dict
1449
Tao Baof2cffbd2015-07-22 12:33:18 -07001450 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001451 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001452
Tao Baof2cffbd2015-07-22 12:33:18 -07001453 if full_recovery_image:
1454 output_sink("etc/recovery.img", recovery_img.data)
1455
1456 else:
1457 diff_program = ["imgdiff"]
1458 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1459 if os.path.exists(path):
1460 diff_program.append("-b")
1461 diff_program.append(path)
1462 bonus_args = "-b /system/etc/recovery-resource.dat"
1463 else:
1464 bonus_args = ""
1465
1466 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1467 _, _, patch = d.ComputePatch()
1468 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001469
Dan Albertebb19aa2015-03-27 19:11:53 -07001470 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001471 # The following GetTypeAndDevice()s need to use the path in the target
1472 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001473 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1474 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1475 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001476 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001477
Tao Baof2cffbd2015-07-22 12:33:18 -07001478 if full_recovery_image:
1479 sh = """#!/system/bin/sh
1480if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1481 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"
1482else
1483 log -t recovery "Recovery image already installed"
1484fi
1485""" % {'type': recovery_type,
1486 'device': recovery_device,
1487 'sha1': recovery_img.sha1,
1488 'size': recovery_img.size}
1489 else:
1490 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001491if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1492 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"
1493else
1494 log -t recovery "Recovery image already installed"
1495fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001496""" % {'boot_size': boot_img.size,
1497 'boot_sha1': boot_img.sha1,
1498 'recovery_size': recovery_img.size,
1499 'recovery_sha1': recovery_img.sha1,
1500 'boot_type': boot_type,
1501 'boot_device': boot_device,
1502 'recovery_type': recovery_type,
1503 'recovery_device': recovery_device,
1504 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001505
1506 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001507 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001508 # target-files expects it to be, and put it there.
1509 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001510 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001511 if system_root_image:
1512 init_rc_dir = os.path.join(input_dir, "ROOT")
1513 else:
1514 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001515 init_rc_files = os.listdir(init_rc_dir)
1516 for init_rc_file in init_rc_files:
1517 if (not init_rc_file.startswith('init.') or
1518 not init_rc_file.endswith('.rc')):
1519 continue
1520
1521 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001522 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001523 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001524 if m:
1525 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001526 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001527 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001528
1529 if found:
1530 break
1531
1532 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001533
1534 output_sink(sh_location, sh)