blob: 87099c21f8b6d12f6391a75a072e44e63ec71ac8 [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 Bao2ed665a2015-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"
Baligh Uddin852a5b52014-11-20 09:52:05 -080052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Dan Albert8b72aef2015-03-23 19:13:21 -070054 self.verbose = False
55 self.tempfiles = []
56 self.device_specific = None
57 self.extras = {}
58 self.info_dict = None
59 self.worker_threads = None
60
61
62OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070063
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080064
65# Values for "certificate" in apkcerts that mean special things.
66SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
67
68
Dan Albert8b72aef2015-03-23 19:13:21 -070069class ExternalError(RuntimeError):
70 pass
Doug Zongkereef39442009-04-02 12:14:19 -070071
72
73def Run(args, **kwargs):
74 """Create and return a subprocess.Popen object, printing the command
75 line on the terminal if -v was specified."""
76 if OPTIONS.verbose:
77 print " running: ", " ".join(args)
78 return subprocess.Popen(args, **kwargs)
79
80
Ying Wang7e6d4e42010-12-13 16:25:36 -080081def CloseInheritedPipes():
82 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
83 before doing other work."""
84 if platform.system() != "Darwin":
85 return
86 for d in range(3, 1025):
87 try:
88 stat = os.fstat(d)
89 if stat is not None:
90 pipebit = stat[0] & 0x1000
91 if pipebit != 0:
92 os.close(d)
93 except OSError:
94 pass
95
96
Tao Bao2c15d9e2015-07-09 11:51:16 -070097def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070098 """Read and parse the META/misc_info.txt key/value pairs from the
99 input target files and return a dict."""
100
Doug Zongkerc9253822014-02-04 12:17:58 -0800101 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700102 if isinstance(input_file, zipfile.ZipFile):
103 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800104 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700105 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800106 try:
107 with open(path) as f:
108 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700109 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800110 if e.errno == errno.ENOENT:
111 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700112 d = {}
113 try:
Michael Runge6e836112014-04-15 17:40:21 -0700114 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700115 except KeyError:
116 # ok if misc_info.txt doesn't exist
117 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700118
Doug Zongker37974732010-09-16 17:44:38 -0700119 # backwards compatibility: These values used to be in their own
120 # files. Look for them, in case we're processing an old
121 # target_files zip.
122
123 if "mkyaffs2_extra_flags" not in d:
124 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700125 d["mkyaffs2_extra_flags"] = read_helper(
126 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700127 except KeyError:
128 # ok if flags don't exist
129 pass
130
131 if "recovery_api_version" not in d:
132 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700133 d["recovery_api_version"] = read_helper(
134 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700135 except KeyError:
136 raise ValueError("can't find recovery API version in input target-files")
137
138 if "tool_extensions" not in d:
139 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800140 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700141 except KeyError:
142 # ok if extensions don't exist
143 pass
144
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800145 if "fstab_version" not in d:
146 d["fstab_version"] = "1"
147
Tao Bao2c15d9e2015-07-09 11:51:16 -0700148 # During building, we use the "file_contexts" in the out/ directory tree.
149 # It is no longer available when (re)generating from target_files zip. So
150 # when generating from target_files zip, we look for a copy under META/
151 # first, if not available search under BOOT/RAMDISK/. Note that we may need
152 # a different file_contexts to build images than the one running on device,
153 # such as when enabling system_root_image. In that case, we must have the
154 # one for building copied to META/.
155 if input_dir is not None:
156 fc_config = os.path.join(input_dir, "META", "file_contexts")
157 if not os.path.exists(fc_config):
158 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", "file_contexts")
159 if not os.path.exists(fc_config):
160 fc_config = None
161
162 if fc_config:
163 d["selinux_fc"] = fc_config
164
Doug Zongker37974732010-09-16 17:44:38 -0700165 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800166 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700167 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700168 if not line:
169 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700170 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700171 if not value:
172 continue
Doug Zongker37974732010-09-16 17:44:38 -0700173 if name == "blocksize":
174 d[name] = value
175 else:
176 d[name + "_size"] = value
177 except KeyError:
178 pass
179
180 def makeint(key):
181 if key in d:
182 d[key] = int(d[key], 0)
183
184 makeint("recovery_api_version")
185 makeint("blocksize")
186 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700187 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700188 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700189 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700190 makeint("recovery_size")
191 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800192 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700193
Doug Zongkerc9253822014-02-04 12:17:58 -0800194 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
195 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700196 return d
197
Doug Zongkerc9253822014-02-04 12:17:58 -0800198def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700199 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800200 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700201 except KeyError:
202 print "Warning: could not find SYSTEM/build.prop in %s" % zip
203 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700204 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700205
Michael Runge6e836112014-04-15 17:40:21 -0700206def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700207 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700208 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700209 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700210 if not line or line.startswith("#"):
211 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700212 if "=" in line:
213 name, value = line.split("=", 1)
214 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700215 return d
216
Doug Zongkerc9253822014-02-04 12:17:58 -0800217def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700218 class Partition(object):
Tao Baodf06e962015-06-10 12:32:41 -0700219 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700220 self.mount_point = mount_point
221 self.fs_type = fs_type
222 self.device = device
223 self.length = length
224 self.device2 = device2
Tao Baodf06e962015-06-10 12:32:41 -0700225 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700226
227 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800228 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700229 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800230 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700231 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700232
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800233 if fstab_version == 1:
234 d = {}
235 for line in data.split("\n"):
236 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700237 if not line or line.startswith("#"):
238 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800239 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700240 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800241 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800242 options = None
243 if len(pieces) >= 4:
244 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700245 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800246 if len(pieces) >= 5:
247 options = pieces[4]
248 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700249 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800250 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800251 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700252 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700253
Dan Albert8b72aef2015-03-23 19:13:21 -0700254 mount_point = pieces[0]
255 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800256 if options:
257 options = options.split(",")
258 for i in options:
259 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700260 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800261 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700262 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800263
Dan Albert8b72aef2015-03-23 19:13:21 -0700264 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
265 device=pieces[2], length=length,
266 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800267
268 elif fstab_version == 2:
269 d = {}
270 for line in data.split("\n"):
271 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700272 if not line or line.startswith("#"):
273 continue
Tao Baodf06e962015-06-10 12:32:41 -0700274 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800275 pieces = line.split()
276 if len(pieces) != 5:
277 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
278
279 # Ignore entries that are managed by vold
280 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700281 if "voldmanaged=" in options:
282 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800283
284 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700285 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800286 options = options.split(",")
287 for i in options:
288 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700289 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800290 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800291 # Ignore all unknown options in the unified fstab
292 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800293
Tao Baodf06e962015-06-10 12:32:41 -0700294 mount_flags = pieces[3]
295 # Honor the SELinux context if present.
296 context = None
297 for i in mount_flags.split(","):
298 if i.startswith("context="):
299 context = i
300
Dan Albert8b72aef2015-03-23 19:13:21 -0700301 mount_point = pieces[1]
302 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Baodf06e962015-06-10 12:32:41 -0700303 device=pieces[0], length=length,
304 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800305
306 else:
307 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
308
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700309 return d
310
311
Doug Zongker37974732010-09-16 17:44:38 -0700312def DumpInfoDict(d):
313 for k, v in sorted(d.items()):
314 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700315
Dan Albert8b72aef2015-03-23 19:13:21 -0700316
Doug Zongkerd5131602012-08-02 14:46:42 -0700317def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700318 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700319 'sourcedir'), and turn them into a boot image. Return the image
320 data, or None if sourcedir does not appear to contains files for
321 building the requested image."""
322
323 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
324 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
325 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700326
Doug Zongkerd5131602012-08-02 14:46:42 -0700327 if info_dict is None:
328 info_dict = OPTIONS.info_dict
329
Doug Zongkereef39442009-04-02 12:14:19 -0700330 ramdisk_img = tempfile.NamedTemporaryFile()
331 img = tempfile.NamedTemporaryFile()
332
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700333 if os.access(fs_config_file, os.F_OK):
334 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
335 else:
336 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
337 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700338 p2 = Run(["minigzip"],
339 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700340
341 p2.wait()
342 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700343 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
344 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700345
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800346 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
347 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
348
349 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700350
Benoit Fradina45a8682014-07-14 21:00:43 +0200351 fn = os.path.join(sourcedir, "second")
352 if os.access(fn, os.F_OK):
353 cmd.append("--second")
354 cmd.append(fn)
355
Doug Zongker171f1cd2009-06-15 22:36:37 -0700356 fn = os.path.join(sourcedir, "cmdline")
357 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700358 cmd.append("--cmdline")
359 cmd.append(open(fn).read().rstrip("\n"))
360
361 fn = os.path.join(sourcedir, "base")
362 if os.access(fn, os.F_OK):
363 cmd.append("--base")
364 cmd.append(open(fn).read().rstrip("\n"))
365
Ying Wang4de6b5b2010-08-25 14:29:34 -0700366 fn = os.path.join(sourcedir, "pagesize")
367 if os.access(fn, os.F_OK):
368 cmd.append("--pagesize")
369 cmd.append(open(fn).read().rstrip("\n"))
370
Doug Zongkerd5131602012-08-02 14:46:42 -0700371 args = info_dict.get("mkbootimg_args", None)
372 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700373 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700374
Tao Baod95e9fd2015-03-29 23:07:41 -0700375 img_unsigned = None
376 if info_dict.get("vboot", None):
377 img_unsigned = tempfile.NamedTemporaryFile()
378 cmd.extend(["--ramdisk", ramdisk_img.name,
379 "--output", img_unsigned.name])
380 else:
381 cmd.extend(["--ramdisk", ramdisk_img.name,
382 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700383
384 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700385 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700386 assert p.returncode == 0, "mkbootimg of %s image failed" % (
387 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700388
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700389 if info_dict.get("verity_key", None):
390 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin852a5b52014-11-20 09:52:05 -0800391 cmd = [OPTIONS.boot_signer_path, path, img.name,
392 info_dict["verity_key"] + ".pk8",
Dan Albert8b72aef2015-03-23 19:13:21 -0700393 info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700394 p = Run(cmd, stdout=subprocess.PIPE)
395 p.communicate()
396 assert p.returncode == 0, "boot_signer of %s image failed" % path
397
Tao Baod95e9fd2015-03-29 23:07:41 -0700398 # Sign the image if vboot is non-empty.
399 elif info_dict.get("vboot", None):
400 path = "/" + os.path.basename(sourcedir).lower()
401 img_keyblock = tempfile.NamedTemporaryFile()
402 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
403 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
404 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
405 img.name]
406 p = Run(cmd, stdout=subprocess.PIPE)
407 p.communicate()
408 assert p.returncode == 0, "vboot_signer of %s image failed" % path
409
Tao Bao2ed665a2015-04-01 11:21:55 -0700410 # Clean up the temp files.
411 img_unsigned.close()
412 img_keyblock.close()
413
Doug Zongkereef39442009-04-02 12:14:19 -0700414 img.seek(os.SEEK_SET, 0)
415 data = img.read()
416
417 ramdisk_img.close()
418 img.close()
419
420 return data
421
422
Doug Zongkerd5131602012-08-02 14:46:42 -0700423def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
424 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800425 """Return a File object (with name 'name') with the desired bootable
426 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700427 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
428 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800429 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700430
Doug Zongker55d93282011-01-25 17:03:34 -0800431 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
432 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700433 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800434 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700435
436 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
437 if os.path.exists(prebuilt_path):
438 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
439 return File.FromLocalFile(name, prebuilt_path)
440
441 print "building image from target_files %s..." % (tree_subdir,)
442 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
443 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
444 os.path.join(unpack_dir, fs_config),
445 info_dict)
446 if data:
447 return File(name, data)
448 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800449
Doug Zongkereef39442009-04-02 12:14:19 -0700450
Doug Zongker75f17362009-12-08 13:46:44 -0800451def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800452 """Unzip the given archive into a temporary directory and return the name.
453
454 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
455 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
456
457 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
458 main file), open for reading.
459 """
Doug Zongkereef39442009-04-02 12:14:19 -0700460
461 tmp = tempfile.mkdtemp(prefix="targetfiles-")
462 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800463
464 def unzip_to_dir(filename, dirname):
465 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
466 if pattern is not None:
467 cmd.append(pattern)
468 p = Run(cmd, stdout=subprocess.PIPE)
469 p.communicate()
470 if p.returncode != 0:
471 raise ExternalError("failed to unzip input target-files \"%s\"" %
472 (filename,))
473
474 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
475 if m:
476 unzip_to_dir(m.group(1), tmp)
477 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
478 filename = m.group(1)
479 else:
480 unzip_to_dir(filename, tmp)
481
482 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700483
484
485def GetKeyPasswords(keylist):
486 """Given a list of keys, prompt the user to enter passwords for
487 those which require them. Return a {key: password} dict. password
488 will be None if the key has no password."""
489
Doug Zongker8ce7c252009-05-22 13:34:54 -0700490 no_passwords = []
491 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700492 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700493 devnull = open("/dev/null", "w+b")
494 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800495 # We don't need a password for things that aren't really keys.
496 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700497 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700498 continue
499
T.R. Fullhart37e10522013-03-18 10:31:26 -0700500 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700501 "-inform", "DER", "-nocrypt"],
502 stdin=devnull.fileno(),
503 stdout=devnull.fileno(),
504 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700505 p.communicate()
506 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700507 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700508 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700509 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700510 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
511 "-inform", "DER", "-passin", "pass:"],
512 stdin=devnull.fileno(),
513 stdout=devnull.fileno(),
514 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700515 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700516 if p.returncode == 0:
517 # Encrypted key with empty string as password.
518 key_passwords[k] = ''
519 elif stderr.startswith('Error decrypting key'):
520 # Definitely encrypted key.
521 # It would have said "Error reading key" if it didn't parse correctly.
522 need_passwords.append(k)
523 else:
524 # Potentially, a type of key that openssl doesn't understand.
525 # We'll let the routines in signapk.jar handle it.
526 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700527 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700528
T.R. Fullhart37e10522013-03-18 10:31:26 -0700529 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700530 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700531 return key_passwords
532
533
Doug Zongker951495f2009-08-14 12:44:19 -0700534def SignFile(input_name, output_name, key, password, align=None,
535 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700536 """Sign the input_name zip/jar/apk, producing output_name. Use the
537 given key and password (the latter may be None if the key does not
538 have a password.
539
540 If align is an integer > 1, zipalign is run to align stored files in
541 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700542
543 If whole_file is true, use the "-w" option to SignApk to embed a
544 signature that covers the whole file in the archive comment of the
545 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700546 """
Doug Zongker951495f2009-08-14 12:44:19 -0700547
Doug Zongkereef39442009-04-02 12:14:19 -0700548 if align == 0 or align == 1:
549 align = None
550
551 if align:
552 temp = tempfile.NamedTemporaryFile()
553 sign_name = temp.name
554 else:
555 sign_name = output_name
556
Baligh Uddin339ee492014-09-05 11:18:07 -0700557 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700558 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
559 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700560 if whole_file:
561 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700562 cmd.extend([key + OPTIONS.public_key_suffix,
563 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700564 input_name, sign_name])
565
566 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700567 if password is not None:
568 password += "\n"
569 p.communicate(password)
570 if p.returncode != 0:
571 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
572
573 if align:
Brian Carlstrom663127d2015-05-22 15:51:19 -0700574 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700575 p.communicate()
576 if p.returncode != 0:
577 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
578 temp.close()
579
580
Doug Zongker37974732010-09-16 17:44:38 -0700581def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700582 """Check the data string passed against the max size limit, if
583 any, for the given target. Raise exception if the data is too big.
584 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700585
Dan Albert8b72aef2015-03-23 19:13:21 -0700586 if target.endswith(".img"):
587 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700588 mount_point = "/" + target
589
Ying Wangf8824af2014-06-03 14:07:27 -0700590 fs_type = None
591 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700592 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700593 if mount_point == "/userdata":
594 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700595 p = info_dict["fstab"][mount_point]
596 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800597 device = p.device
598 if "/" in device:
599 device = device[device.rfind("/")+1:]
600 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700601 if not fs_type or not limit:
602 return
Doug Zongkereef39442009-04-02 12:14:19 -0700603
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700604 if fs_type == "yaffs2":
605 # image size should be increased by 1/64th to account for the
606 # spare area (64 bytes per 2k page)
607 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800608 size = len(data)
609 pct = float(size) * 100.0 / limit
610 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
611 if pct >= 99.0:
612 raise ExternalError(msg)
613 elif pct >= 95.0:
614 print
615 print " WARNING: ", msg
616 print
617 elif OPTIONS.verbose:
618 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700619
620
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800621def ReadApkCerts(tf_zip):
622 """Given a target_files ZipFile, parse the META/apkcerts.txt file
623 and return a {package: cert} dict."""
624 certmap = {}
625 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
626 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700627 if not line:
628 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800629 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
630 r'private_key="(.*)"$', line)
631 if m:
632 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700633 public_key_suffix_len = len(OPTIONS.public_key_suffix)
634 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800635 if cert in SPECIAL_CERT_STRINGS and not privkey:
636 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700637 elif (cert.endswith(OPTIONS.public_key_suffix) and
638 privkey.endswith(OPTIONS.private_key_suffix) and
639 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
640 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800641 else:
642 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
643 return certmap
644
645
Doug Zongkereef39442009-04-02 12:14:19 -0700646COMMON_DOCSTRING = """
647 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700648 Prepend <dir>/bin to the list of places to search for binaries
649 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700650
Doug Zongker05d3dea2009-06-22 11:32:31 -0700651 -s (--device_specific) <file>
652 Path to the python module containing device-specific
653 releasetools code.
654
Doug Zongker8bec09e2009-11-30 15:37:14 -0800655 -x (--extra) <key=value>
656 Add a key/value pair to the 'extras' dict, which device-specific
657 extension code may look at.
658
Doug Zongkereef39442009-04-02 12:14:19 -0700659 -v (--verbose)
660 Show command lines being executed.
661
662 -h (--help)
663 Display this usage message and exit.
664"""
665
666def Usage(docstring):
667 print docstring.rstrip("\n")
668 print COMMON_DOCSTRING
669
670
671def ParseOptions(argv,
672 docstring,
673 extra_opts="", extra_long_opts=(),
674 extra_option_handler=None):
675 """Parse the options in argv and return any arguments that aren't
676 flags. docstring is the calling module's docstring, to be displayed
677 for errors and -h. extra_opts and extra_long_opts are for flags
678 defined by the caller, which are processed by passing them to
679 extra_option_handler."""
680
681 try:
682 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800683 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700684 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700685 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin852a5b52014-11-20 09:52:05 -0800686 "private_key_suffix=", "boot_signer_path=", "device_specific=",
687 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700688 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700689 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700690 Usage(docstring)
691 print "**", str(err), "**"
692 sys.exit(2)
693
Doug Zongkereef39442009-04-02 12:14:19 -0700694 for o, a in opts:
695 if o in ("-h", "--help"):
696 Usage(docstring)
697 sys.exit()
698 elif o in ("-v", "--verbose"):
699 OPTIONS.verbose = True
700 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700701 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700702 elif o in ("--signapk_path",):
703 OPTIONS.signapk_path = a
704 elif o in ("--extra_signapk_args",):
705 OPTIONS.extra_signapk_args = shlex.split(a)
706 elif o in ("--java_path",):
707 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700708 elif o in ("--java_args",):
709 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700710 elif o in ("--public_key_suffix",):
711 OPTIONS.public_key_suffix = a
712 elif o in ("--private_key_suffix",):
713 OPTIONS.private_key_suffix = a
Baligh Uddin852a5b52014-11-20 09:52:05 -0800714 elif o in ("--boot_signer_path",):
715 OPTIONS.boot_signer_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700716 elif o in ("-s", "--device_specific"):
717 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800718 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800719 key, value = a.split("=", 1)
720 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700721 else:
722 if extra_option_handler is None or not extra_option_handler(o, a):
723 assert False, "unknown option \"%s\"" % (o,)
724
Doug Zongker85448772014-09-09 14:59:20 -0700725 if OPTIONS.search_path:
726 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
727 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700728
729 return args
730
731
Doug Zongkerfc44a512014-08-26 13:10:25 -0700732def MakeTempFile(prefix=None, suffix=None):
733 """Make a temp file and add it to the list of things to be deleted
734 when Cleanup() is called. Return the filename."""
735 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
736 os.close(fd)
737 OPTIONS.tempfiles.append(fn)
738 return fn
739
740
Doug Zongkereef39442009-04-02 12:14:19 -0700741def Cleanup():
742 for i in OPTIONS.tempfiles:
743 if os.path.isdir(i):
744 shutil.rmtree(i)
745 else:
746 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700747
748
749class PasswordManager(object):
750 def __init__(self):
751 self.editor = os.getenv("EDITOR", None)
752 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
753
754 def GetPasswords(self, items):
755 """Get passwords corresponding to each string in 'items',
756 returning a dict. (The dict may have keys in addition to the
757 values in 'items'.)
758
759 Uses the passwords in $ANDROID_PW_FILE if available, letting the
760 user edit that file to add more needed passwords. If no editor is
761 available, or $ANDROID_PW_FILE isn't define, prompts the user
762 interactively in the ordinary way.
763 """
764
765 current = self.ReadFile()
766
767 first = True
768 while True:
769 missing = []
770 for i in items:
771 if i not in current or not current[i]:
772 missing.append(i)
773 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700774 if not missing:
775 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700776
777 for i in missing:
778 current[i] = ""
779
780 if not first:
781 print "key file %s still missing some passwords." % (self.pwfile,)
782 answer = raw_input("try to edit again? [y]> ").strip()
783 if answer and answer[0] not in 'yY':
784 raise RuntimeError("key passwords unavailable")
785 first = False
786
787 current = self.UpdateAndReadFile(current)
788
Dan Albert8b72aef2015-03-23 19:13:21 -0700789 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700790 """Prompt the user to enter a value (password) for each key in
791 'current' whose value is fales. Returns a new dict with all the
792 values.
793 """
794 result = {}
795 for k, v in sorted(current.iteritems()):
796 if v:
797 result[k] = v
798 else:
799 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700800 result[k] = getpass.getpass(
801 "Enter password for %s key> " % k).strip()
802 if result[k]:
803 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700804 return result
805
806 def UpdateAndReadFile(self, current):
807 if not self.editor or not self.pwfile:
808 return self.PromptResult(current)
809
810 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700811 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700812 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
813 f.write("# (Additional spaces are harmless.)\n\n")
814
815 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700816 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
817 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700818 f.write("[[[ %s ]]] %s\n" % (v, k))
819 if not v and first_line is None:
820 # position cursor on first line with no password.
821 first_line = i + 4
822 f.close()
823
824 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
825 _, _ = p.communicate()
826
827 return self.ReadFile()
828
829 def ReadFile(self):
830 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700831 if self.pwfile is None:
832 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700833 try:
834 f = open(self.pwfile, "r")
835 for line in f:
836 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700837 if not line or line[0] == '#':
838 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700839 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
840 if not m:
841 print "failed to parse password file: ", line
842 else:
843 result[m.group(2)] = m.group(1)
844 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700845 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700846 if e.errno != errno.ENOENT:
847 print "error reading password file: ", str(e)
848 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700849
850
Dan Albert8e0178d2015-01-27 15:53:15 -0800851def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
852 compress_type=None):
853 import datetime
854
855 # http://b/18015246
856 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
857 # for files larger than 2GiB. We can work around this by adjusting their
858 # limit. Note that `zipfile.writestr()` will not work for strings larger than
859 # 2GiB. The Python interpreter sometimes rejects strings that large (though
860 # it isn't clear to me exactly what circumstances cause this).
861 # `zipfile.write()` must be used directly to work around this.
862 #
863 # This mess can be avoided if we port to python3.
864 saved_zip64_limit = zipfile.ZIP64_LIMIT
865 zipfile.ZIP64_LIMIT = (1 << 32) - 1
866
867 if compress_type is None:
868 compress_type = zip_file.compression
869 if arcname is None:
870 arcname = filename
871
872 saved_stat = os.stat(filename)
873
874 try:
875 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
876 # file to be zipped and reset it when we're done.
877 os.chmod(filename, perms)
878
879 # Use a fixed timestamp so the output is repeatable.
880 epoch = datetime.datetime.fromtimestamp(0)
881 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
882 os.utime(filename, (timestamp, timestamp))
883
884 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
885 finally:
886 os.chmod(filename, saved_stat.st_mode)
887 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
888 zipfile.ZIP64_LIMIT = saved_zip64_limit
889
890
Tao Bao97734652015-05-20 09:32:18 -0700891def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Bao2ed665a2015-04-01 11:21:55 -0700892 compress_type=None):
893 """Wrap zipfile.writestr() function to work around the zip64 limit.
894
895 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
896 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
897 when calling crc32(bytes).
898
899 But it still works fine to write a shorter string into a large zip file.
900 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
901 when we know the string won't be too long.
902 """
903
904 saved_zip64_limit = zipfile.ZIP64_LIMIT
905 zipfile.ZIP64_LIMIT = (1 << 32) - 1
906
907 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
908 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700909 zinfo.compress_type = zip_file.compression
Tao Bao97734652015-05-20 09:32:18 -0700910 if perms is None:
911 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800912 else:
Tao Bao2ed665a2015-04-01 11:21:55 -0700913 zinfo = zinfo_or_arcname
914
915 # If compress_type is given, it overrides the value in zinfo.
916 if compress_type is not None:
917 zinfo.compress_type = compress_type
918
Tao Bao97734652015-05-20 09:32:18 -0700919 # If perms is given, it has a priority.
920 if perms is not None:
921 zinfo.external_attr = perms << 16
922
Tao Bao2ed665a2015-04-01 11:21:55 -0700923 # Use a fixed timestamp so the output is repeatable.
Tao Bao2ed665a2015-04-01 11:21:55 -0700924 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
925
Dan Albert8b72aef2015-03-23 19:13:21 -0700926 zip_file.writestr(zinfo, data)
Tao Bao2ed665a2015-04-01 11:21:55 -0700927 zipfile.ZIP64_LIMIT = saved_zip64_limit
928
929
930def ZipClose(zip_file):
931 # http://b/18015246
932 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
933 # central directory.
934 saved_zip64_limit = zipfile.ZIP64_LIMIT
935 zipfile.ZIP64_LIMIT = (1 << 32) - 1
936
937 zip_file.close()
938
939 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700940
941
942class DeviceSpecificParams(object):
943 module = None
944 def __init__(self, **kwargs):
945 """Keyword arguments to the constructor become attributes of this
946 object, which is passed to all functions in the device-specific
947 module."""
948 for k, v in kwargs.iteritems():
949 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800950 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700951
952 if self.module is None:
953 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700954 if not path:
955 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700956 try:
957 if os.path.isdir(path):
958 info = imp.find_module("releasetools", [path])
959 else:
960 d, f = os.path.split(path)
961 b, x = os.path.splitext(f)
962 if x == ".py":
963 f = b
964 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800965 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700966 self.module = imp.load_module("device_specific", *info)
967 except ImportError:
968 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700969
970 def _DoCall(self, function_name, *args, **kwargs):
971 """Call the named function in the device-specific module, passing
972 the given args and kwargs. The first argument to the call will be
973 the DeviceSpecific object itself. If there is no module, or the
974 module does not define the function, return the value of the
975 'default' kwarg (which itself defaults to None)."""
976 if self.module is None or not hasattr(self.module, function_name):
977 return kwargs.get("default", None)
978 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
979
980 def FullOTA_Assertions(self):
981 """Called after emitting the block of assertions at the top of a
982 full OTA package. Implementations can add whatever additional
983 assertions they like."""
984 return self._DoCall("FullOTA_Assertions")
985
Doug Zongkere5ff5902012-01-17 10:55:37 -0800986 def FullOTA_InstallBegin(self):
987 """Called at the start of full OTA installation."""
988 return self._DoCall("FullOTA_InstallBegin")
989
Doug Zongker05d3dea2009-06-22 11:32:31 -0700990 def FullOTA_InstallEnd(self):
991 """Called at the end of full OTA installation; typically this is
992 used to install the image for the device's baseband processor."""
993 return self._DoCall("FullOTA_InstallEnd")
994
995 def IncrementalOTA_Assertions(self):
996 """Called after emitting the block of assertions at the top of an
997 incremental OTA package. Implementations can add whatever
998 additional assertions they like."""
999 return self._DoCall("IncrementalOTA_Assertions")
1000
Doug Zongkere5ff5902012-01-17 10:55:37 -08001001 def IncrementalOTA_VerifyBegin(self):
1002 """Called at the start of the verification phase of incremental
1003 OTA installation; additional checks can be placed here to abort
1004 the script before any changes are made."""
1005 return self._DoCall("IncrementalOTA_VerifyBegin")
1006
Doug Zongker05d3dea2009-06-22 11:32:31 -07001007 def IncrementalOTA_VerifyEnd(self):
1008 """Called at the end of the verification phase of incremental OTA
1009 installation; additional checks can be placed here to abort the
1010 script before any changes are made."""
1011 return self._DoCall("IncrementalOTA_VerifyEnd")
1012
Doug Zongkere5ff5902012-01-17 10:55:37 -08001013 def IncrementalOTA_InstallBegin(self):
1014 """Called at the start of incremental OTA installation (after
1015 verification is complete)."""
1016 return self._DoCall("IncrementalOTA_InstallBegin")
1017
Doug Zongker05d3dea2009-06-22 11:32:31 -07001018 def IncrementalOTA_InstallEnd(self):
1019 """Called at the end of incremental OTA installation; typically
1020 this is used to install the image for the device's baseband
1021 processor."""
1022 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001023
1024class File(object):
1025 def __init__(self, name, data):
1026 self.name = name
1027 self.data = data
1028 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001029 self.sha1 = sha1(data).hexdigest()
1030
1031 @classmethod
1032 def FromLocalFile(cls, name, diskname):
1033 f = open(diskname, "rb")
1034 data = f.read()
1035 f.close()
1036 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001037
1038 def WriteToTemp(self):
1039 t = tempfile.NamedTemporaryFile()
1040 t.write(self.data)
1041 t.flush()
1042 return t
1043
Geremy Condra36bd3652014-02-06 19:45:10 -08001044 def AddToZip(self, z, compression=None):
Tao Bao2ed665a2015-04-01 11:21:55 -07001045 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001046
1047DIFF_PROGRAM_BY_EXT = {
1048 ".gz" : "imgdiff",
1049 ".zip" : ["imgdiff", "-z"],
1050 ".jar" : ["imgdiff", "-z"],
1051 ".apk" : ["imgdiff", "-z"],
1052 ".img" : "imgdiff",
1053 }
1054
1055class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001056 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001057 self.tf = tf
1058 self.sf = sf
1059 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001060 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001061
1062 def ComputePatch(self):
1063 """Compute the patch (as a string of data) needed to turn sf into
1064 tf. Returns the same tuple as GetPatch()."""
1065
1066 tf = self.tf
1067 sf = self.sf
1068
Doug Zongker24cd2802012-08-14 16:36:15 -07001069 if self.diff_program:
1070 diff_program = self.diff_program
1071 else:
1072 ext = os.path.splitext(tf.name)[1]
1073 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001074
1075 ttemp = tf.WriteToTemp()
1076 stemp = sf.WriteToTemp()
1077
1078 ext = os.path.splitext(tf.name)[1]
1079
1080 try:
1081 ptemp = tempfile.NamedTemporaryFile()
1082 if isinstance(diff_program, list):
1083 cmd = copy.copy(diff_program)
1084 else:
1085 cmd = [diff_program]
1086 cmd.append(stemp.name)
1087 cmd.append(ttemp.name)
1088 cmd.append(ptemp.name)
1089 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001090 err = []
1091 def run():
1092 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001093 if e:
1094 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001095 th = threading.Thread(target=run)
1096 th.start()
1097 th.join(timeout=300) # 5 mins
1098 if th.is_alive():
1099 print "WARNING: diff command timed out"
1100 p.terminate()
1101 th.join(5)
1102 if th.is_alive():
1103 p.kill()
1104 th.join()
1105
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001106 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001107 print "WARNING: failure running %s:\n%s\n" % (
1108 diff_program, "".join(err))
1109 self.patch = None
1110 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001111 diff = ptemp.read()
1112 finally:
1113 ptemp.close()
1114 stemp.close()
1115 ttemp.close()
1116
1117 self.patch = diff
1118 return self.tf, self.sf, self.patch
1119
1120
1121 def GetPatch(self):
1122 """Return a tuple (target_file, source_file, patch_data).
1123 patch_data may be None if ComputePatch hasn't been called, or if
1124 computing the patch failed."""
1125 return self.tf, self.sf, self.patch
1126
1127
1128def ComputeDifferences(diffs):
1129 """Call ComputePatch on all the Difference objects in 'diffs'."""
1130 print len(diffs), "diffs to compute"
1131
1132 # Do the largest files first, to try and reduce the long-pole effect.
1133 by_size = [(i.tf.size, i) for i in diffs]
1134 by_size.sort(reverse=True)
1135 by_size = [i[1] for i in by_size]
1136
1137 lock = threading.Lock()
1138 diff_iter = iter(by_size) # accessed under lock
1139
1140 def worker():
1141 try:
1142 lock.acquire()
1143 for d in diff_iter:
1144 lock.release()
1145 start = time.time()
1146 d.ComputePatch()
1147 dur = time.time() - start
1148 lock.acquire()
1149
1150 tf, sf, patch = d.GetPatch()
1151 if sf.name == tf.name:
1152 name = tf.name
1153 else:
1154 name = "%s (%s)" % (tf.name, sf.name)
1155 if patch is None:
1156 print "patching failed! %s" % (name,)
1157 else:
1158 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1159 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1160 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001161 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001162 print e
1163 raise
1164
1165 # start worker threads; wait for them all to finish.
1166 threads = [threading.Thread(target=worker)
1167 for i in range(OPTIONS.worker_threads)]
1168 for th in threads:
1169 th.start()
1170 while threads:
1171 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001172
1173
Dan Albert8b72aef2015-03-23 19:13:21 -07001174class BlockDifference(object):
1175 def __init__(self, partition, tgt, src=None, check_first_block=False,
1176 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001177 self.tgt = tgt
1178 self.src = src
1179 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001180 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001181
Tao Baoff777812015-05-12 11:42:31 -07001182 # Due to http://b/20939131, check_first_block is disabled temporarily.
1183 assert not self.check_first_block
1184
Tao Baodd2a5892015-03-12 12:32:37 -07001185 if version is None:
1186 version = 1
1187 if OPTIONS.info_dict:
1188 version = max(
1189 int(i) for i in
1190 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1191 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001192
1193 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001194 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001195 tmpdir = tempfile.mkdtemp()
1196 OPTIONS.tempfiles.append(tmpdir)
1197 self.path = os.path.join(tmpdir, partition)
1198 b.Compute(self.path)
1199
1200 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1201
1202 def WriteScript(self, script, output_zip, progress=None):
1203 if not self.src:
1204 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001205 script.Print("Patching %s image unconditionally..." % (self.partition,))
1206 else:
1207 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001208
Dan Albert8b72aef2015-03-23 19:13:21 -07001209 if progress:
1210 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001211 self._WriteUpdate(script, output_zip)
Tao Bao68658c02015-06-01 13:40:49 -07001212 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001213
1214 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001215 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001216 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001217 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001218 else:
Tao Baoff777812015-05-12 11:42:31 -07001219 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1220 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001221 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001222 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1223 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001224 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001225 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baoff777812015-05-12 11:42:31 -07001226 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001227 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001228 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001229 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Baoff777812015-05-12 11:42:31 -07001230 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001231 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001232 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001233
Tao Baodd2a5892015-03-12 12:32:37 -07001234 # When generating incrementals for the system and vendor partitions,
1235 # explicitly check the first block (which contains the superblock) of
1236 # the partition to see if it's what we expect. If this check fails,
1237 # give an explicit log message about the partition having been
1238 # remounted R/W (the most likely explanation) and the need to flash to
1239 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001240 if self.check_first_block:
1241 self._CheckFirstBlock(script)
1242
Tao Baodd2a5892015-03-12 12:32:37 -07001243 # Abort the OTA update. Note that the incremental OTA cannot be applied
1244 # even if it may match the checksum of the target partition.
1245 # a) If version < 3, operations like move and erase will make changes
1246 # unconditionally and damage the partition.
1247 # b) If version >= 3, it won't even reach here.
1248 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1249 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001250
Tao Bao68658c02015-06-01 13:40:49 -07001251 def _WritePostInstallVerifyScript(self, script):
1252 partition = self.partition
1253 script.Print('Verifying the updated %s image...' % (partition,))
1254 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1255 ranges = self.tgt.care_map
1256 ranges_str = ranges.to_string_raw()
1257 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1258 self.device, ranges_str,
1259 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001260
1261 # Bug: 20881595
1262 # Verify that extended blocks are really zeroed out.
1263 if self.tgt.extended:
1264 ranges_str = self.tgt.extended.to_string_raw()
1265 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1266 self.device, ranges_str,
1267 self._HashZeroBlocks(self.tgt.extended.size())))
1268 script.Print('Verified the updated %s image.' % (partition,))
1269 script.AppendExtra(
1270 'else\n'
1271 ' abort("%s partition has unexpected non-zero contents after OTA '
1272 'update");\n'
1273 'endif;' % (partition,))
1274 else:
1275 script.Print('Verified the updated %s image.' % (partition,))
1276
Tao Bao68658c02015-06-01 13:40:49 -07001277 script.AppendExtra(
1278 'else\n'
1279 ' abort("%s partition has unexpected contents after OTA update");\n'
1280 'endif;' % (partition,))
1281
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001282 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001283 ZipWrite(output_zip,
1284 '{}.transfer.list'.format(self.path),
1285 '{}.transfer.list'.format(self.partition))
1286 ZipWrite(output_zip,
1287 '{}.new.dat'.format(self.path),
1288 '{}.new.dat'.format(self.partition))
1289 ZipWrite(output_zip,
1290 '{}.patch.dat'.format(self.path),
1291 '{}.patch.dat'.format(self.partition),
1292 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001293
Dan Albert8e0178d2015-01-27 15:53:15 -08001294 call = ('block_image_update("{device}", '
1295 'package_extract_file("{partition}.transfer.list"), '
1296 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1297 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001298 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001299
Dan Albert8b72aef2015-03-23 19:13:21 -07001300 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001301 data = source.ReadRangeSet(ranges)
1302 ctx = sha1()
1303
1304 for p in data:
1305 ctx.update(p)
1306
1307 return ctx.hexdigest()
1308
Tao Baoe9b61912015-07-09 17:37:49 -07001309 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1310 """Return the hash value for all zero blocks."""
1311 zero_block = '\x00' * 4096
1312 ctx = sha1()
1313 for _ in range(num_blocks):
1314 ctx.update(zero_block)
1315
1316 return ctx.hexdigest()
1317
Tao Baoff777812015-05-12 11:42:31 -07001318 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1319 # remounting R/W. Will change the checking to a finer-grained way to
1320 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001321 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001322 r = rangelib.RangeSet((0, 1))
1323 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001324
1325 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1326 'abort("%s has been remounted R/W; '
1327 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001328 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001329 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001330
1331DataImage = blockimgdiff.DataImage
1332
1333
Doug Zongker96a57e72010-09-26 14:57:41 -07001334# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001335PARTITION_TYPES = {
1336 "yaffs2": "MTD",
1337 "mtd": "MTD",
1338 "ext4": "EMMC",
1339 "emmc": "EMMC",
Mohamad Ayyasha9905342015-05-01 15:39:36 -07001340 "f2fs": "EMMC",
1341 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001342}
Doug Zongker96a57e72010-09-26 14:57:41 -07001343
1344def GetTypeAndDevice(mount_point, info):
1345 fstab = info["fstab"]
1346 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001347 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1348 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001349 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001350 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001351
1352
1353def ParseCertificate(data):
1354 """Parse a PEM-format certificate."""
1355 cert = []
1356 save = False
1357 for line in data.split("\n"):
1358 if "--END CERTIFICATE--" in line:
1359 break
1360 if save:
1361 cert.append(line)
1362 if "--BEGIN CERTIFICATE--" in line:
1363 save = True
1364 cert = "".join(cert).decode('base64')
1365 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001366
Doug Zongker412c02f2014-02-13 10:58:24 -08001367def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1368 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001369 """Generate a binary patch that creates the recovery image starting
1370 with the boot image. (Most of the space in these images is just the
1371 kernel, which is identical for the two, so the resulting patch
1372 should be efficient.) Add it to the output zip, along with a shell
1373 script that is run from init.rc on first boot to actually do the
1374 patching and install the new recovery image.
1375
1376 recovery_img and boot_img should be File objects for the
1377 corresponding images. info should be the dictionary returned by
1378 common.LoadInfoDict() on the input target_files.
1379 """
1380
Doug Zongker412c02f2014-02-13 10:58:24 -08001381 if info_dict is None:
1382 info_dict = OPTIONS.info_dict
1383
Doug Zongkerc9253822014-02-04 12:17:58 -08001384 diff_program = ["imgdiff"]
1385 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1386 if os.path.exists(path):
1387 diff_program.append("-b")
1388 diff_program.append(path)
1389 bonus_args = "-b /system/etc/recovery-resource.dat"
1390 else:
1391 bonus_args = ""
1392
1393 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1394 _, _, patch = d.ComputePatch()
1395 output_sink("recovery-from-boot.p", patch)
1396
Dan Albertebb19aa2015-03-27 19:11:53 -07001397 try:
1398 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1399 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1400 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001401 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001402
1403 sh = """#!/system/bin/sh
1404if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1405 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"
1406else
1407 log -t recovery "Recovery image already installed"
1408fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001409""" % {'boot_size': boot_img.size,
1410 'boot_sha1': boot_img.sha1,
1411 'recovery_size': recovery_img.size,
1412 'recovery_sha1': recovery_img.sha1,
1413 'boot_type': boot_type,
1414 'boot_device': boot_device,
1415 'recovery_type': recovery_type,
1416 'recovery_device': recovery_device,
1417 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001418
1419 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001420 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001421 # target-files expects it to be, and put it there.
1422 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001423 found = False
1424 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
1425 init_rc_files = os.listdir(init_rc_dir)
1426 for init_rc_file in init_rc_files:
1427 if (not init_rc_file.startswith('init.') or
1428 not init_rc_file.endswith('.rc')):
1429 continue
1430
1431 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001432 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001433 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001434 if m:
1435 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001436 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001437 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001438
1439 if found:
1440 break
1441
1442 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001443
1444 output_sink(sh_location, sh)