blob: fe40a72821489aa9a90a5430004a6e0bf3dc4f53 [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
62 self.worker_threads = None
63
64
65OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070066
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080067
68# Values for "certificate" in apkcerts that mean special things.
69SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
70
71
Dan Albert8b72aef2015-03-23 19:13:21 -070072class ExternalError(RuntimeError):
73 pass
Doug Zongkereef39442009-04-02 12:14:19 -070074
75
76def Run(args, **kwargs):
77 """Create and return a subprocess.Popen object, printing the command
78 line on the terminal if -v was specified."""
79 if OPTIONS.verbose:
80 print " running: ", " ".join(args)
81 return subprocess.Popen(args, **kwargs)
82
83
Ying Wang7e6d4e42010-12-13 16:25:36 -080084def CloseInheritedPipes():
85 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
86 before doing other work."""
87 if platform.system() != "Darwin":
88 return
89 for d in range(3, 1025):
90 try:
91 stat = os.fstat(d)
92 if stat is not None:
93 pipebit = stat[0] & 0x1000
94 if pipebit != 0:
95 os.close(d)
96 except OSError:
97 pass
98
99
Tao Bao2c15d9e2015-07-09 11:51:16 -0700100def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700101 """Read and parse the META/misc_info.txt key/value pairs from the
102 input target files and return a dict."""
103
Doug Zongkerc9253822014-02-04 12:17:58 -0800104 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700105 if isinstance(input_file, zipfile.ZipFile):
106 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800107 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700108 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800109 try:
110 with open(path) as f:
111 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700112 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800113 if e.errno == errno.ENOENT:
114 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700115 d = {}
116 try:
Michael Runge6e836112014-04-15 17:40:21 -0700117 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700118 except KeyError:
119 # ok if misc_info.txt doesn't exist
120 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700121
Doug Zongker37974732010-09-16 17:44:38 -0700122 # backwards compatibility: These values used to be in their own
123 # files. Look for them, in case we're processing an old
124 # target_files zip.
125
126 if "mkyaffs2_extra_flags" not in d:
127 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700128 d["mkyaffs2_extra_flags"] = read_helper(
129 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700130 except KeyError:
131 # ok if flags don't exist
132 pass
133
134 if "recovery_api_version" not in d:
135 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700136 d["recovery_api_version"] = read_helper(
137 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700138 except KeyError:
139 raise ValueError("can't find recovery API version in input target-files")
140
141 if "tool_extensions" not in d:
142 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800143 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700144 except KeyError:
145 # ok if extensions don't exist
146 pass
147
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800148 if "fstab_version" not in d:
149 d["fstab_version"] = "1"
150
Tao Bao84e75682015-07-19 02:38:53 -0700151 # A few properties are stored as links to the files in the out/ directory.
152 # It works fine with the build system. However, they are no longer available
153 # when (re)generating from target_files zip. If input_dir is not None, we
154 # are doing repacking. Redirect those properties to the actual files in the
155 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700156 if input_dir is not None:
Tao Bao84e75682015-07-19 02:38:53 -0700157 # We carry a copy of file_contexts under META/. If not available, search
158 # BOOT/RAMDISK/. Note that sometimes we may need a different file_contexts
159 # to build images than the one running on device, such as when enabling
160 # system_root_image. In that case, we must have the one for image
161 # generation copied to META/.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700162 fc_config = os.path.join(input_dir, "META", "file_contexts")
Tao Bao84e75682015-07-19 02:38:53 -0700163 if d.get("system_root_image") == "true":
164 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700165 if not os.path.exists(fc_config):
166 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", "file_contexts")
167 if not os.path.exists(fc_config):
168 fc_config = None
169
170 if fc_config:
171 d["selinux_fc"] = fc_config
172
Tao Bao84e75682015-07-19 02:38:53 -0700173 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
174 if d.get("system_root_image") == "true":
175 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
176 d["ramdisk_fs_config"] = os.path.join(
177 input_dir, "META", "root_filesystem_config.txt")
178
Doug Zongker37974732010-09-16 17:44:38 -0700179 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800180 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700181 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700182 if not line:
183 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700184 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700185 if not value:
186 continue
Doug Zongker37974732010-09-16 17:44:38 -0700187 if name == "blocksize":
188 d[name] = value
189 else:
190 d[name + "_size"] = value
191 except KeyError:
192 pass
193
194 def makeint(key):
195 if key in d:
196 d[key] = int(d[key], 0)
197
198 makeint("recovery_api_version")
199 makeint("blocksize")
200 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700201 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700202 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700203 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700204 makeint("recovery_size")
205 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800206 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700207
Doug Zongkerc9253822014-02-04 12:17:58 -0800208 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
209 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700210 return d
211
Doug Zongkerc9253822014-02-04 12:17:58 -0800212def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700213 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800214 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700215 except KeyError:
216 print "Warning: could not find SYSTEM/build.prop in %s" % zip
217 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700218 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700219
Michael Runge6e836112014-04-15 17:40:21 -0700220def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700221 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700222 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700223 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700224 if not line or line.startswith("#"):
225 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700226 if "=" in line:
227 name, value = line.split("=", 1)
228 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700229 return d
230
Doug Zongkerc9253822014-02-04 12:17:58 -0800231def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700232 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700233 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700234 self.mount_point = mount_point
235 self.fs_type = fs_type
236 self.device = device
237 self.length = length
238 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700239 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700240
241 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800242 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700243 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800244 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700245 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700246
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800247 if fstab_version == 1:
248 d = {}
249 for line in data.split("\n"):
250 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700251 if not line or line.startswith("#"):
252 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800253 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700254 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800255 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800256 options = None
257 if len(pieces) >= 4:
258 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700259 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800260 if len(pieces) >= 5:
261 options = pieces[4]
262 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700263 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800264 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800265 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700266 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700267
Dan Albert8b72aef2015-03-23 19:13:21 -0700268 mount_point = pieces[0]
269 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800270 if options:
271 options = options.split(",")
272 for i in options:
273 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700274 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800275 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700276 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800277
Dan Albert8b72aef2015-03-23 19:13:21 -0700278 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
279 device=pieces[2], length=length,
280 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800281
282 elif fstab_version == 2:
283 d = {}
284 for line in data.split("\n"):
285 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700286 if not line or line.startswith("#"):
287 continue
Tao Bao548eb762015-06-10 12:32:41 -0700288 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800289 pieces = line.split()
290 if len(pieces) != 5:
291 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
292
293 # Ignore entries that are managed by vold
294 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700295 if "voldmanaged=" in options:
296 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800297
298 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700299 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800300 options = options.split(",")
301 for i in options:
302 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700303 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800304 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800305 # Ignore all unknown options in the unified fstab
306 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800307
Tao Bao548eb762015-06-10 12:32:41 -0700308 mount_flags = pieces[3]
309 # Honor the SELinux context if present.
310 context = None
311 for i in mount_flags.split(","):
312 if i.startswith("context="):
313 context = i
314
Dan Albert8b72aef2015-03-23 19:13:21 -0700315 mount_point = pieces[1]
316 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700317 device=pieces[0], length=length,
318 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800319
320 else:
321 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
322
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700323 return d
324
325
Doug Zongker37974732010-09-16 17:44:38 -0700326def DumpInfoDict(d):
327 for k, v in sorted(d.items()):
328 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700329
Dan Albert8b72aef2015-03-23 19:13:21 -0700330
Doug Zongkerd5131602012-08-02 14:46:42 -0700331def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700332 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700333 'sourcedir'), and turn them into a boot image. Return the image
334 data, or None if sourcedir does not appear to contains files for
335 building the requested image."""
336
337 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
338 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
339 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700340
Doug Zongkerd5131602012-08-02 14:46:42 -0700341 if info_dict is None:
342 info_dict = OPTIONS.info_dict
343
Doug Zongkereef39442009-04-02 12:14:19 -0700344 ramdisk_img = tempfile.NamedTemporaryFile()
345 img = tempfile.NamedTemporaryFile()
346
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700347 if os.access(fs_config_file, os.F_OK):
348 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
349 else:
350 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
351 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700352 p2 = Run(["minigzip"],
353 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700354
355 p2.wait()
356 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700357 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
358 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700359
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800360 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
361 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
362
363 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700364
Benoit Fradina45a8682014-07-14 21:00:43 +0200365 fn = os.path.join(sourcedir, "second")
366 if os.access(fn, os.F_OK):
367 cmd.append("--second")
368 cmd.append(fn)
369
Doug Zongker171f1cd2009-06-15 22:36:37 -0700370 fn = os.path.join(sourcedir, "cmdline")
371 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700372 cmd.append("--cmdline")
373 cmd.append(open(fn).read().rstrip("\n"))
374
375 fn = os.path.join(sourcedir, "base")
376 if os.access(fn, os.F_OK):
377 cmd.append("--base")
378 cmd.append(open(fn).read().rstrip("\n"))
379
Ying Wang4de6b5b2010-08-25 14:29:34 -0700380 fn = os.path.join(sourcedir, "pagesize")
381 if os.access(fn, os.F_OK):
382 cmd.append("--pagesize")
383 cmd.append(open(fn).read().rstrip("\n"))
384
Doug Zongkerd5131602012-08-02 14:46:42 -0700385 args = info_dict.get("mkbootimg_args", None)
386 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700387 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700388
Tao Baod95e9fd2015-03-29 23:07:41 -0700389 img_unsigned = None
390 if info_dict.get("vboot", None):
391 img_unsigned = tempfile.NamedTemporaryFile()
392 cmd.extend(["--ramdisk", ramdisk_img.name,
393 "--output", img_unsigned.name])
394 else:
395 cmd.extend(["--ramdisk", ramdisk_img.name,
396 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700397
398 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700399 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700400 assert p.returncode == 0, "mkbootimg of %s image failed" % (
401 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700402
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100403 if (info_dict.get("boot_signer", None) == "true" and
404 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700405 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700406 cmd = [OPTIONS.boot_signer_path]
407 cmd.extend(OPTIONS.boot_signer_args)
408 cmd.extend([path, img.name,
409 info_dict["verity_key"] + ".pk8",
410 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700411 p = Run(cmd, stdout=subprocess.PIPE)
412 p.communicate()
413 assert p.returncode == 0, "boot_signer of %s image failed" % path
414
Tao Baod95e9fd2015-03-29 23:07:41 -0700415 # Sign the image if vboot is non-empty.
416 elif info_dict.get("vboot", None):
417 path = "/" + os.path.basename(sourcedir).lower()
418 img_keyblock = tempfile.NamedTemporaryFile()
419 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
420 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
421 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
422 img.name]
423 p = Run(cmd, stdout=subprocess.PIPE)
424 p.communicate()
425 assert p.returncode == 0, "vboot_signer of %s image failed" % path
426
Tao Baof3282b42015-04-01 11:21:55 -0700427 # Clean up the temp files.
428 img_unsigned.close()
429 img_keyblock.close()
430
Doug Zongkereef39442009-04-02 12:14:19 -0700431 img.seek(os.SEEK_SET, 0)
432 data = img.read()
433
434 ramdisk_img.close()
435 img.close()
436
437 return data
438
439
Doug Zongkerd5131602012-08-02 14:46:42 -0700440def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
441 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800442 """Return a File object (with name 'name') with the desired bootable
443 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700444 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
445 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800446 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700447
Doug Zongker55d93282011-01-25 17:03:34 -0800448 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
449 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700450 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800451 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700452
453 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
454 if os.path.exists(prebuilt_path):
455 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
456 return File.FromLocalFile(name, prebuilt_path)
457
458 print "building image from target_files %s..." % (tree_subdir,)
459 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
460 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
461 os.path.join(unpack_dir, fs_config),
462 info_dict)
463 if data:
464 return File(name, data)
465 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800466
Doug Zongkereef39442009-04-02 12:14:19 -0700467
Doug Zongker75f17362009-12-08 13:46:44 -0800468def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800469 """Unzip the given archive into a temporary directory and return the name.
470
471 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
472 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
473
474 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
475 main file), open for reading.
476 """
Doug Zongkereef39442009-04-02 12:14:19 -0700477
478 tmp = tempfile.mkdtemp(prefix="targetfiles-")
479 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800480
481 def unzip_to_dir(filename, dirname):
482 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
483 if pattern is not None:
484 cmd.append(pattern)
485 p = Run(cmd, stdout=subprocess.PIPE)
486 p.communicate()
487 if p.returncode != 0:
488 raise ExternalError("failed to unzip input target-files \"%s\"" %
489 (filename,))
490
491 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
492 if m:
493 unzip_to_dir(m.group(1), tmp)
494 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
495 filename = m.group(1)
496 else:
497 unzip_to_dir(filename, tmp)
498
499 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700500
501
502def GetKeyPasswords(keylist):
503 """Given a list of keys, prompt the user to enter passwords for
504 those which require them. Return a {key: password} dict. password
505 will be None if the key has no password."""
506
Doug Zongker8ce7c252009-05-22 13:34:54 -0700507 no_passwords = []
508 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700509 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700510 devnull = open("/dev/null", "w+b")
511 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800512 # We don't need a password for things that aren't really keys.
513 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700514 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700515 continue
516
T.R. Fullhart37e10522013-03-18 10:31:26 -0700517 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700518 "-inform", "DER", "-nocrypt"],
519 stdin=devnull.fileno(),
520 stdout=devnull.fileno(),
521 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700522 p.communicate()
523 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700524 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700525 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700526 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700527 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
528 "-inform", "DER", "-passin", "pass:"],
529 stdin=devnull.fileno(),
530 stdout=devnull.fileno(),
531 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700532 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700533 if p.returncode == 0:
534 # Encrypted key with empty string as password.
535 key_passwords[k] = ''
536 elif stderr.startswith('Error decrypting key'):
537 # Definitely encrypted key.
538 # It would have said "Error reading key" if it didn't parse correctly.
539 need_passwords.append(k)
540 else:
541 # Potentially, a type of key that openssl doesn't understand.
542 # We'll let the routines in signapk.jar handle it.
543 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700544 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700545
T.R. Fullhart37e10522013-03-18 10:31:26 -0700546 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700547 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700548 return key_passwords
549
550
Doug Zongker951495f2009-08-14 12:44:19 -0700551def SignFile(input_name, output_name, key, password, align=None,
552 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700553 """Sign the input_name zip/jar/apk, producing output_name. Use the
554 given key and password (the latter may be None if the key does not
555 have a password.
556
557 If align is an integer > 1, zipalign is run to align stored files in
558 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700559
560 If whole_file is true, use the "-w" option to SignApk to embed a
561 signature that covers the whole file in the archive comment of the
562 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700563 """
Doug Zongker951495f2009-08-14 12:44:19 -0700564
Doug Zongkereef39442009-04-02 12:14:19 -0700565 if align == 0 or align == 1:
566 align = None
567
568 if align:
569 temp = tempfile.NamedTemporaryFile()
570 sign_name = temp.name
571 else:
572 sign_name = output_name
573
Baligh Uddin339ee492014-09-05 11:18:07 -0700574 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700575 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
576 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700577 if whole_file:
578 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700579 cmd.extend([key + OPTIONS.public_key_suffix,
580 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700581 input_name, sign_name])
582
583 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700584 if password is not None:
585 password += "\n"
586 p.communicate(password)
587 if p.returncode != 0:
588 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
589
590 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700591 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700592 p.communicate()
593 if p.returncode != 0:
594 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
595 temp.close()
596
597
Doug Zongker37974732010-09-16 17:44:38 -0700598def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700599 """Check the data string passed against the max size limit, if
600 any, for the given target. Raise exception if the data is too big.
601 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700602
Dan Albert8b72aef2015-03-23 19:13:21 -0700603 if target.endswith(".img"):
604 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700605 mount_point = "/" + target
606
Ying Wangf8824af2014-06-03 14:07:27 -0700607 fs_type = None
608 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700609 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700610 if mount_point == "/userdata":
611 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700612 p = info_dict["fstab"][mount_point]
613 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800614 device = p.device
615 if "/" in device:
616 device = device[device.rfind("/")+1:]
617 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700618 if not fs_type or not limit:
619 return
Doug Zongkereef39442009-04-02 12:14:19 -0700620
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700621 if fs_type == "yaffs2":
622 # image size should be increased by 1/64th to account for the
623 # spare area (64 bytes per 2k page)
624 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800625 size = len(data)
626 pct = float(size) * 100.0 / limit
627 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
628 if pct >= 99.0:
629 raise ExternalError(msg)
630 elif pct >= 95.0:
631 print
632 print " WARNING: ", msg
633 print
634 elif OPTIONS.verbose:
635 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700636
637
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800638def ReadApkCerts(tf_zip):
639 """Given a target_files ZipFile, parse the META/apkcerts.txt file
640 and return a {package: cert} dict."""
641 certmap = {}
642 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
643 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700644 if not line:
645 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800646 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
647 r'private_key="(.*)"$', line)
648 if m:
649 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700650 public_key_suffix_len = len(OPTIONS.public_key_suffix)
651 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800652 if cert in SPECIAL_CERT_STRINGS and not privkey:
653 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700654 elif (cert.endswith(OPTIONS.public_key_suffix) and
655 privkey.endswith(OPTIONS.private_key_suffix) and
656 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
657 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800658 else:
659 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
660 return certmap
661
662
Doug Zongkereef39442009-04-02 12:14:19 -0700663COMMON_DOCSTRING = """
664 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700665 Prepend <dir>/bin to the list of places to search for binaries
666 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700667
Doug Zongker05d3dea2009-06-22 11:32:31 -0700668 -s (--device_specific) <file>
669 Path to the python module containing device-specific
670 releasetools code.
671
Doug Zongker8bec09e2009-11-30 15:37:14 -0800672 -x (--extra) <key=value>
673 Add a key/value pair to the 'extras' dict, which device-specific
674 extension code may look at.
675
Doug Zongkereef39442009-04-02 12:14:19 -0700676 -v (--verbose)
677 Show command lines being executed.
678
679 -h (--help)
680 Display this usage message and exit.
681"""
682
683def Usage(docstring):
684 print docstring.rstrip("\n")
685 print COMMON_DOCSTRING
686
687
688def ParseOptions(argv,
689 docstring,
690 extra_opts="", extra_long_opts=(),
691 extra_option_handler=None):
692 """Parse the options in argv and return any arguments that aren't
693 flags. docstring is the calling module's docstring, to be displayed
694 for errors and -h. extra_opts and extra_long_opts are for flags
695 defined by the caller, which are processed by passing them to
696 extra_option_handler."""
697
698 try:
699 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800700 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700701 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700702 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700703 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
704 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800705 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700706 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700707 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700708 Usage(docstring)
709 print "**", str(err), "**"
710 sys.exit(2)
711
Doug Zongkereef39442009-04-02 12:14:19 -0700712 for o, a in opts:
713 if o in ("-h", "--help"):
714 Usage(docstring)
715 sys.exit()
716 elif o in ("-v", "--verbose"):
717 OPTIONS.verbose = True
718 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700719 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700720 elif o in ("--signapk_path",):
721 OPTIONS.signapk_path = a
722 elif o in ("--extra_signapk_args",):
723 OPTIONS.extra_signapk_args = shlex.split(a)
724 elif o in ("--java_path",):
725 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700726 elif o in ("--java_args",):
727 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700728 elif o in ("--public_key_suffix",):
729 OPTIONS.public_key_suffix = a
730 elif o in ("--private_key_suffix",):
731 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800732 elif o in ("--boot_signer_path",):
733 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700734 elif o in ("--boot_signer_args",):
735 OPTIONS.boot_signer_args = shlex.split(a)
736 elif o in ("--verity_signer_path",):
737 OPTIONS.verity_signer_path = a
738 elif o in ("--verity_signer_args",):
739 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700740 elif o in ("-s", "--device_specific"):
741 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800742 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800743 key, value = a.split("=", 1)
744 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700745 else:
746 if extra_option_handler is None or not extra_option_handler(o, a):
747 assert False, "unknown option \"%s\"" % (o,)
748
Doug Zongker85448772014-09-09 14:59:20 -0700749 if OPTIONS.search_path:
750 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
751 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700752
753 return args
754
755
Doug Zongkerfc44a512014-08-26 13:10:25 -0700756def MakeTempFile(prefix=None, suffix=None):
757 """Make a temp file and add it to the list of things to be deleted
758 when Cleanup() is called. Return the filename."""
759 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
760 os.close(fd)
761 OPTIONS.tempfiles.append(fn)
762 return fn
763
764
Doug Zongkereef39442009-04-02 12:14:19 -0700765def Cleanup():
766 for i in OPTIONS.tempfiles:
767 if os.path.isdir(i):
768 shutil.rmtree(i)
769 else:
770 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700771
772
773class PasswordManager(object):
774 def __init__(self):
775 self.editor = os.getenv("EDITOR", None)
776 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
777
778 def GetPasswords(self, items):
779 """Get passwords corresponding to each string in 'items',
780 returning a dict. (The dict may have keys in addition to the
781 values in 'items'.)
782
783 Uses the passwords in $ANDROID_PW_FILE if available, letting the
784 user edit that file to add more needed passwords. If no editor is
785 available, or $ANDROID_PW_FILE isn't define, prompts the user
786 interactively in the ordinary way.
787 """
788
789 current = self.ReadFile()
790
791 first = True
792 while True:
793 missing = []
794 for i in items:
795 if i not in current or not current[i]:
796 missing.append(i)
797 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700798 if not missing:
799 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700800
801 for i in missing:
802 current[i] = ""
803
804 if not first:
805 print "key file %s still missing some passwords." % (self.pwfile,)
806 answer = raw_input("try to edit again? [y]> ").strip()
807 if answer and answer[0] not in 'yY':
808 raise RuntimeError("key passwords unavailable")
809 first = False
810
811 current = self.UpdateAndReadFile(current)
812
Dan Albert8b72aef2015-03-23 19:13:21 -0700813 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700814 """Prompt the user to enter a value (password) for each key in
815 'current' whose value is fales. Returns a new dict with all the
816 values.
817 """
818 result = {}
819 for k, v in sorted(current.iteritems()):
820 if v:
821 result[k] = v
822 else:
823 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700824 result[k] = getpass.getpass(
825 "Enter password for %s key> " % k).strip()
826 if result[k]:
827 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700828 return result
829
830 def UpdateAndReadFile(self, current):
831 if not self.editor or not self.pwfile:
832 return self.PromptResult(current)
833
834 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700835 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700836 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
837 f.write("# (Additional spaces are harmless.)\n\n")
838
839 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700840 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
841 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700842 f.write("[[[ %s ]]] %s\n" % (v, k))
843 if not v and first_line is None:
844 # position cursor on first line with no password.
845 first_line = i + 4
846 f.close()
847
848 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
849 _, _ = p.communicate()
850
851 return self.ReadFile()
852
853 def ReadFile(self):
854 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700855 if self.pwfile is None:
856 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700857 try:
858 f = open(self.pwfile, "r")
859 for line in f:
860 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700861 if not line or line[0] == '#':
862 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700863 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
864 if not m:
865 print "failed to parse password file: ", line
866 else:
867 result[m.group(2)] = m.group(1)
868 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700869 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700870 if e.errno != errno.ENOENT:
871 print "error reading password file: ", str(e)
872 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700873
874
Dan Albert8e0178d2015-01-27 15:53:15 -0800875def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
876 compress_type=None):
877 import datetime
878
879 # http://b/18015246
880 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
881 # for files larger than 2GiB. We can work around this by adjusting their
882 # limit. Note that `zipfile.writestr()` will not work for strings larger than
883 # 2GiB. The Python interpreter sometimes rejects strings that large (though
884 # it isn't clear to me exactly what circumstances cause this).
885 # `zipfile.write()` must be used directly to work around this.
886 #
887 # This mess can be avoided if we port to python3.
888 saved_zip64_limit = zipfile.ZIP64_LIMIT
889 zipfile.ZIP64_LIMIT = (1 << 32) - 1
890
891 if compress_type is None:
892 compress_type = zip_file.compression
893 if arcname is None:
894 arcname = filename
895
896 saved_stat = os.stat(filename)
897
898 try:
899 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
900 # file to be zipped and reset it when we're done.
901 os.chmod(filename, perms)
902
903 # Use a fixed timestamp so the output is repeatable.
904 epoch = datetime.datetime.fromtimestamp(0)
905 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
906 os.utime(filename, (timestamp, timestamp))
907
908 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
909 finally:
910 os.chmod(filename, saved_stat.st_mode)
911 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
912 zipfile.ZIP64_LIMIT = saved_zip64_limit
913
914
Tao Bao58c1b962015-05-20 09:32:18 -0700915def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700916 compress_type=None):
917 """Wrap zipfile.writestr() function to work around the zip64 limit.
918
919 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
920 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
921 when calling crc32(bytes).
922
923 But it still works fine to write a shorter string into a large zip file.
924 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
925 when we know the string won't be too long.
926 """
927
928 saved_zip64_limit = zipfile.ZIP64_LIMIT
929 zipfile.ZIP64_LIMIT = (1 << 32) - 1
930
931 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
932 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700933 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700934 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700935 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800936 else:
Tao Baof3282b42015-04-01 11:21:55 -0700937 zinfo = zinfo_or_arcname
938
939 # If compress_type is given, it overrides the value in zinfo.
940 if compress_type is not None:
941 zinfo.compress_type = compress_type
942
Tao Bao58c1b962015-05-20 09:32:18 -0700943 # If perms is given, it has a priority.
944 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700945 # If perms doesn't set the file type, mark it as a regular file.
946 if perms & 0o770000 == 0:
947 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -0700948 zinfo.external_attr = perms << 16
949
Tao Baof3282b42015-04-01 11:21:55 -0700950 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700951 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
952
Dan Albert8b72aef2015-03-23 19:13:21 -0700953 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700954 zipfile.ZIP64_LIMIT = saved_zip64_limit
955
956
957def ZipClose(zip_file):
958 # http://b/18015246
959 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
960 # central directory.
961 saved_zip64_limit = zipfile.ZIP64_LIMIT
962 zipfile.ZIP64_LIMIT = (1 << 32) - 1
963
964 zip_file.close()
965
966 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700967
968
969class DeviceSpecificParams(object):
970 module = None
971 def __init__(self, **kwargs):
972 """Keyword arguments to the constructor become attributes of this
973 object, which is passed to all functions in the device-specific
974 module."""
975 for k, v in kwargs.iteritems():
976 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800977 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700978
979 if self.module is None:
980 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700981 if not path:
982 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700983 try:
984 if os.path.isdir(path):
985 info = imp.find_module("releasetools", [path])
986 else:
987 d, f = os.path.split(path)
988 b, x = os.path.splitext(f)
989 if x == ".py":
990 f = b
991 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800992 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700993 self.module = imp.load_module("device_specific", *info)
994 except ImportError:
995 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700996
997 def _DoCall(self, function_name, *args, **kwargs):
998 """Call the named function in the device-specific module, passing
999 the given args and kwargs. The first argument to the call will be
1000 the DeviceSpecific object itself. If there is no module, or the
1001 module does not define the function, return the value of the
1002 'default' kwarg (which itself defaults to None)."""
1003 if self.module is None or not hasattr(self.module, function_name):
1004 return kwargs.get("default", None)
1005 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1006
1007 def FullOTA_Assertions(self):
1008 """Called after emitting the block of assertions at the top of a
1009 full OTA package. Implementations can add whatever additional
1010 assertions they like."""
1011 return self._DoCall("FullOTA_Assertions")
1012
Doug Zongkere5ff5902012-01-17 10:55:37 -08001013 def FullOTA_InstallBegin(self):
1014 """Called at the start of full OTA installation."""
1015 return self._DoCall("FullOTA_InstallBegin")
1016
Doug Zongker05d3dea2009-06-22 11:32:31 -07001017 def FullOTA_InstallEnd(self):
1018 """Called at the end of full OTA installation; typically this is
1019 used to install the image for the device's baseband processor."""
1020 return self._DoCall("FullOTA_InstallEnd")
1021
1022 def IncrementalOTA_Assertions(self):
1023 """Called after emitting the block of assertions at the top of an
1024 incremental OTA package. Implementations can add whatever
1025 additional assertions they like."""
1026 return self._DoCall("IncrementalOTA_Assertions")
1027
Doug Zongkere5ff5902012-01-17 10:55:37 -08001028 def IncrementalOTA_VerifyBegin(self):
1029 """Called at the start of the verification phase of incremental
1030 OTA installation; additional checks can be placed here to abort
1031 the script before any changes are made."""
1032 return self._DoCall("IncrementalOTA_VerifyBegin")
1033
Doug Zongker05d3dea2009-06-22 11:32:31 -07001034 def IncrementalOTA_VerifyEnd(self):
1035 """Called at the end of the verification phase of incremental OTA
1036 installation; additional checks can be placed here to abort the
1037 script before any changes are made."""
1038 return self._DoCall("IncrementalOTA_VerifyEnd")
1039
Doug Zongkere5ff5902012-01-17 10:55:37 -08001040 def IncrementalOTA_InstallBegin(self):
1041 """Called at the start of incremental OTA installation (after
1042 verification is complete)."""
1043 return self._DoCall("IncrementalOTA_InstallBegin")
1044
Doug Zongker05d3dea2009-06-22 11:32:31 -07001045 def IncrementalOTA_InstallEnd(self):
1046 """Called at the end of incremental OTA installation; typically
1047 this is used to install the image for the device's baseband
1048 processor."""
1049 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001050
1051class File(object):
1052 def __init__(self, name, data):
1053 self.name = name
1054 self.data = data
1055 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001056 self.sha1 = sha1(data).hexdigest()
1057
1058 @classmethod
1059 def FromLocalFile(cls, name, diskname):
1060 f = open(diskname, "rb")
1061 data = f.read()
1062 f.close()
1063 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001064
1065 def WriteToTemp(self):
1066 t = tempfile.NamedTemporaryFile()
1067 t.write(self.data)
1068 t.flush()
1069 return t
1070
Geremy Condra36bd3652014-02-06 19:45:10 -08001071 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001072 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001073
1074DIFF_PROGRAM_BY_EXT = {
1075 ".gz" : "imgdiff",
1076 ".zip" : ["imgdiff", "-z"],
1077 ".jar" : ["imgdiff", "-z"],
1078 ".apk" : ["imgdiff", "-z"],
1079 ".img" : "imgdiff",
1080 }
1081
1082class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001083 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001084 self.tf = tf
1085 self.sf = sf
1086 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001087 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001088
1089 def ComputePatch(self):
1090 """Compute the patch (as a string of data) needed to turn sf into
1091 tf. Returns the same tuple as GetPatch()."""
1092
1093 tf = self.tf
1094 sf = self.sf
1095
Doug Zongker24cd2802012-08-14 16:36:15 -07001096 if self.diff_program:
1097 diff_program = self.diff_program
1098 else:
1099 ext = os.path.splitext(tf.name)[1]
1100 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001101
1102 ttemp = tf.WriteToTemp()
1103 stemp = sf.WriteToTemp()
1104
1105 ext = os.path.splitext(tf.name)[1]
1106
1107 try:
1108 ptemp = tempfile.NamedTemporaryFile()
1109 if isinstance(diff_program, list):
1110 cmd = copy.copy(diff_program)
1111 else:
1112 cmd = [diff_program]
1113 cmd.append(stemp.name)
1114 cmd.append(ttemp.name)
1115 cmd.append(ptemp.name)
1116 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001117 err = []
1118 def run():
1119 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001120 if e:
1121 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001122 th = threading.Thread(target=run)
1123 th.start()
1124 th.join(timeout=300) # 5 mins
1125 if th.is_alive():
1126 print "WARNING: diff command timed out"
1127 p.terminate()
1128 th.join(5)
1129 if th.is_alive():
1130 p.kill()
1131 th.join()
1132
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001133 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001134 print "WARNING: failure running %s:\n%s\n" % (
1135 diff_program, "".join(err))
1136 self.patch = None
1137 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001138 diff = ptemp.read()
1139 finally:
1140 ptemp.close()
1141 stemp.close()
1142 ttemp.close()
1143
1144 self.patch = diff
1145 return self.tf, self.sf, self.patch
1146
1147
1148 def GetPatch(self):
1149 """Return a tuple (target_file, source_file, patch_data).
1150 patch_data may be None if ComputePatch hasn't been called, or if
1151 computing the patch failed."""
1152 return self.tf, self.sf, self.patch
1153
1154
1155def ComputeDifferences(diffs):
1156 """Call ComputePatch on all the Difference objects in 'diffs'."""
1157 print len(diffs), "diffs to compute"
1158
1159 # Do the largest files first, to try and reduce the long-pole effect.
1160 by_size = [(i.tf.size, i) for i in diffs]
1161 by_size.sort(reverse=True)
1162 by_size = [i[1] for i in by_size]
1163
1164 lock = threading.Lock()
1165 diff_iter = iter(by_size) # accessed under lock
1166
1167 def worker():
1168 try:
1169 lock.acquire()
1170 for d in diff_iter:
1171 lock.release()
1172 start = time.time()
1173 d.ComputePatch()
1174 dur = time.time() - start
1175 lock.acquire()
1176
1177 tf, sf, patch = d.GetPatch()
1178 if sf.name == tf.name:
1179 name = tf.name
1180 else:
1181 name = "%s (%s)" % (tf.name, sf.name)
1182 if patch is None:
1183 print "patching failed! %s" % (name,)
1184 else:
1185 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1186 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1187 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001188 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001189 print e
1190 raise
1191
1192 # start worker threads; wait for them all to finish.
1193 threads = [threading.Thread(target=worker)
1194 for i in range(OPTIONS.worker_threads)]
1195 for th in threads:
1196 th.start()
1197 while threads:
1198 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001199
1200
Dan Albert8b72aef2015-03-23 19:13:21 -07001201class BlockDifference(object):
1202 def __init__(self, partition, tgt, src=None, check_first_block=False,
1203 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001204 self.tgt = tgt
1205 self.src = src
1206 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001207 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001208
Tao Bao5ece99d2015-05-12 11:42:31 -07001209 # Due to http://b/20939131, check_first_block is disabled temporarily.
1210 assert not self.check_first_block
1211
Tao Baodd2a5892015-03-12 12:32:37 -07001212 if version is None:
1213 version = 1
1214 if OPTIONS.info_dict:
1215 version = max(
1216 int(i) for i in
1217 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1218 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001219
1220 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001221 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001222 tmpdir = tempfile.mkdtemp()
1223 OPTIONS.tempfiles.append(tmpdir)
1224 self.path = os.path.join(tmpdir, partition)
1225 b.Compute(self.path)
1226
1227 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1228
1229 def WriteScript(self, script, output_zip, progress=None):
1230 if not self.src:
1231 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001232 script.Print("Patching %s image unconditionally..." % (self.partition,))
1233 else:
1234 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001235
Dan Albert8b72aef2015-03-23 19:13:21 -07001236 if progress:
1237 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001238 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001239 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001240
1241 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001242 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001243 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001244 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001245 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001246 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1247 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001248 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001249 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1250 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001251 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001252 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001253 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001254 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001255 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001256 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001257 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001258 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001259 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001260
Tao Baodd2a5892015-03-12 12:32:37 -07001261 # When generating incrementals for the system and vendor partitions,
1262 # explicitly check the first block (which contains the superblock) of
1263 # the partition to see if it's what we expect. If this check fails,
1264 # give an explicit log message about the partition having been
1265 # remounted R/W (the most likely explanation) and the need to flash to
1266 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001267 if self.check_first_block:
1268 self._CheckFirstBlock(script)
1269
Tao Baodd2a5892015-03-12 12:32:37 -07001270 # Abort the OTA update. Note that the incremental OTA cannot be applied
1271 # even if it may match the checksum of the target partition.
1272 # a) If version < 3, operations like move and erase will make changes
1273 # unconditionally and damage the partition.
1274 # b) If version >= 3, it won't even reach here.
1275 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1276 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001277
Tao Bao5fcaaef2015-06-01 13:40:49 -07001278 def _WritePostInstallVerifyScript(self, script):
1279 partition = self.partition
1280 script.Print('Verifying the updated %s image...' % (partition,))
1281 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1282 ranges = self.tgt.care_map
1283 ranges_str = ranges.to_string_raw()
1284 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1285 self.device, ranges_str,
1286 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001287
1288 # Bug: 20881595
1289 # Verify that extended blocks are really zeroed out.
1290 if self.tgt.extended:
1291 ranges_str = self.tgt.extended.to_string_raw()
1292 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1293 self.device, ranges_str,
1294 self._HashZeroBlocks(self.tgt.extended.size())))
1295 script.Print('Verified the updated %s image.' % (partition,))
1296 script.AppendExtra(
1297 'else\n'
1298 ' abort("%s partition has unexpected non-zero contents after OTA '
1299 'update");\n'
1300 'endif;' % (partition,))
1301 else:
1302 script.Print('Verified the updated %s image.' % (partition,))
1303
Tao Bao5fcaaef2015-06-01 13:40:49 -07001304 script.AppendExtra(
1305 'else\n'
1306 ' abort("%s partition has unexpected contents after OTA update");\n'
1307 'endif;' % (partition,))
1308
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001309 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001310 ZipWrite(output_zip,
1311 '{}.transfer.list'.format(self.path),
1312 '{}.transfer.list'.format(self.partition))
1313 ZipWrite(output_zip,
1314 '{}.new.dat'.format(self.path),
1315 '{}.new.dat'.format(self.partition))
1316 ZipWrite(output_zip,
1317 '{}.patch.dat'.format(self.path),
1318 '{}.patch.dat'.format(self.partition),
1319 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001320
Dan Albert8e0178d2015-01-27 15:53:15 -08001321 call = ('block_image_update("{device}", '
1322 'package_extract_file("{partition}.transfer.list"), '
1323 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1324 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001325 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001326
Dan Albert8b72aef2015-03-23 19:13:21 -07001327 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001328 data = source.ReadRangeSet(ranges)
1329 ctx = sha1()
1330
1331 for p in data:
1332 ctx.update(p)
1333
1334 return ctx.hexdigest()
1335
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001336 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1337 """Return the hash value for all zero blocks."""
1338 zero_block = '\x00' * 4096
1339 ctx = sha1()
1340 for _ in range(num_blocks):
1341 ctx.update(zero_block)
1342
1343 return ctx.hexdigest()
1344
Tao Bao5ece99d2015-05-12 11:42:31 -07001345 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1346 # remounting R/W. Will change the checking to a finer-grained way to
1347 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001348 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001349 r = rangelib.RangeSet((0, 1))
1350 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001351
1352 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1353 'abort("%s has been remounted R/W; '
1354 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001355 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001356 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001357
1358DataImage = blockimgdiff.DataImage
1359
1360
Doug Zongker96a57e72010-09-26 14:57:41 -07001361# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001362PARTITION_TYPES = {
1363 "yaffs2": "MTD",
1364 "mtd": "MTD",
1365 "ext4": "EMMC",
1366 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001367 "f2fs": "EMMC",
1368 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001369}
Doug Zongker96a57e72010-09-26 14:57:41 -07001370
1371def GetTypeAndDevice(mount_point, info):
1372 fstab = info["fstab"]
1373 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001374 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1375 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001376 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001377 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001378
1379
1380def ParseCertificate(data):
1381 """Parse a PEM-format certificate."""
1382 cert = []
1383 save = False
1384 for line in data.split("\n"):
1385 if "--END CERTIFICATE--" in line:
1386 break
1387 if save:
1388 cert.append(line)
1389 if "--BEGIN CERTIFICATE--" in line:
1390 save = True
1391 cert = "".join(cert).decode('base64')
1392 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001393
Doug Zongker412c02f2014-02-13 10:58:24 -08001394def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1395 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001396 """Generate a binary patch that creates the recovery image starting
1397 with the boot image. (Most of the space in these images is just the
1398 kernel, which is identical for the two, so the resulting patch
1399 should be efficient.) Add it to the output zip, along with a shell
1400 script that is run from init.rc on first boot to actually do the
1401 patching and install the new recovery image.
1402
1403 recovery_img and boot_img should be File objects for the
1404 corresponding images. info should be the dictionary returned by
1405 common.LoadInfoDict() on the input target_files.
1406 """
1407
Doug Zongker412c02f2014-02-13 10:58:24 -08001408 if info_dict is None:
1409 info_dict = OPTIONS.info_dict
1410
Doug Zongkerc9253822014-02-04 12:17:58 -08001411 diff_program = ["imgdiff"]
1412 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1413 if os.path.exists(path):
1414 diff_program.append("-b")
1415 diff_program.append(path)
1416 bonus_args = "-b /system/etc/recovery-resource.dat"
1417 else:
1418 bonus_args = ""
1419
1420 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1421 _, _, patch = d.ComputePatch()
1422 output_sink("recovery-from-boot.p", patch)
1423
Dan Albertebb19aa2015-03-27 19:11:53 -07001424 try:
1425 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1426 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1427 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001428 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001429
1430 sh = """#!/system/bin/sh
1431if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1432 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"
1433else
1434 log -t recovery "Recovery image already installed"
1435fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001436""" % {'boot_size': boot_img.size,
1437 'boot_sha1': boot_img.sha1,
1438 'recovery_size': recovery_img.size,
1439 'recovery_sha1': recovery_img.sha1,
1440 'boot_type': boot_type,
1441 'boot_device': boot_device,
1442 'recovery_type': recovery_type,
1443 'recovery_device': recovery_device,
1444 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001445
1446 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001447 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001448 # target-files expects it to be, and put it there.
1449 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001450 found = False
1451 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
1452 init_rc_files = os.listdir(init_rc_dir)
1453 for init_rc_file in init_rc_files:
1454 if (not init_rc_file.startswith('init.') or
1455 not init_rc_file.endswith('.rc')):
1456 continue
1457
1458 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001459 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001460 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001461 if m:
1462 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001463 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001464 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001465
1466 if found:
1467 break
1468
1469 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001470
1471 output_sink(sh_location, sh)