blob: 0d8ca343f13e7dab5cda23c20dd611518ef4abdf [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 Bao84e75682015-07-19 02:38:53 -0700148 # A few properties are stored as links to the files in the out/ directory.
149 # It works fine with the build system. However, they are no longer available
150 # when (re)generating from target_files zip. If input_dir is not None, we
151 # are doing repacking. Redirect those properties to the actual files in the
152 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700153 if input_dir is not None:
Tao Bao84e75682015-07-19 02:38:53 -0700154 # We carry a copy of file_contexts under META/. If not available, search
155 # BOOT/RAMDISK/. Note that sometimes we may need a different file_contexts
156 # to build images than the one running on device, such as when enabling
157 # system_root_image. In that case, we must have the one for image
158 # generation copied to META/.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700159 fc_config = os.path.join(input_dir, "META", "file_contexts")
Tao Bao84e75682015-07-19 02:38:53 -0700160 if d.get("system_root_image") == "true":
161 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700162 if not os.path.exists(fc_config):
163 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", "file_contexts")
164 if not os.path.exists(fc_config):
165 fc_config = None
166
167 if fc_config:
168 d["selinux_fc"] = fc_config
169
Tao Bao84e75682015-07-19 02:38:53 -0700170 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
171 if d.get("system_root_image") == "true":
172 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
173 d["ramdisk_fs_config"] = os.path.join(
174 input_dir, "META", "root_filesystem_config.txt")
175
Doug Zongker37974732010-09-16 17:44:38 -0700176 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800177 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700178 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700179 if not line:
180 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700181 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700182 if not value:
183 continue
Doug Zongker37974732010-09-16 17:44:38 -0700184 if name == "blocksize":
185 d[name] = value
186 else:
187 d[name + "_size"] = value
188 except KeyError:
189 pass
190
191 def makeint(key):
192 if key in d:
193 d[key] = int(d[key], 0)
194
195 makeint("recovery_api_version")
196 makeint("blocksize")
197 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700198 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700199 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700200 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700201 makeint("recovery_size")
202 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800203 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700204
Doug Zongkerc9253822014-02-04 12:17:58 -0800205 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
206 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700207 return d
208
Doug Zongkerc9253822014-02-04 12:17:58 -0800209def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700210 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800211 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700212 except KeyError:
213 print "Warning: could not find SYSTEM/build.prop in %s" % zip
214 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700215 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700216
Michael Runge6e836112014-04-15 17:40:21 -0700217def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700218 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700219 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700220 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700221 if not line or line.startswith("#"):
222 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700223 if "=" in line:
224 name, value = line.split("=", 1)
225 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700226 return d
227
Doug Zongkerc9253822014-02-04 12:17:58 -0800228def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700229 class Partition(object):
Tao Baodf06e962015-06-10 12:32:41 -0700230 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 self.mount_point = mount_point
232 self.fs_type = fs_type
233 self.device = device
234 self.length = length
235 self.device2 = device2
Tao Baodf06e962015-06-10 12:32:41 -0700236 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700237
238 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800239 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700240 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800241 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700242 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700243
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800244 if fstab_version == 1:
245 d = {}
246 for line in data.split("\n"):
247 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700248 if not line or line.startswith("#"):
249 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800250 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700251 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800252 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800253 options = None
254 if len(pieces) >= 4:
255 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700256 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800257 if len(pieces) >= 5:
258 options = pieces[4]
259 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700260 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800261 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800262 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700263 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700264
Dan Albert8b72aef2015-03-23 19:13:21 -0700265 mount_point = pieces[0]
266 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800267 if options:
268 options = options.split(",")
269 for i in options:
270 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700271 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800272 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700273 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800274
Dan Albert8b72aef2015-03-23 19:13:21 -0700275 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
276 device=pieces[2], length=length,
277 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800278
279 elif fstab_version == 2:
280 d = {}
281 for line in data.split("\n"):
282 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700283 if not line or line.startswith("#"):
284 continue
Tao Baodf06e962015-06-10 12:32:41 -0700285 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800286 pieces = line.split()
287 if len(pieces) != 5:
288 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
289
290 # Ignore entries that are managed by vold
291 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700292 if "voldmanaged=" in options:
293 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800294
295 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700296 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800297 options = options.split(",")
298 for i in options:
299 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700300 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800301 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800302 # Ignore all unknown options in the unified fstab
303 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800304
Tao Baodf06e962015-06-10 12:32:41 -0700305 mount_flags = pieces[3]
306 # Honor the SELinux context if present.
307 context = None
308 for i in mount_flags.split(","):
309 if i.startswith("context="):
310 context = i
311
Dan Albert8b72aef2015-03-23 19:13:21 -0700312 mount_point = pieces[1]
313 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Baodf06e962015-06-10 12:32:41 -0700314 device=pieces[0], length=length,
315 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800316
317 else:
318 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
319
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700320 return d
321
322
Doug Zongker37974732010-09-16 17:44:38 -0700323def DumpInfoDict(d):
324 for k, v in sorted(d.items()):
325 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700326
Dan Albert8b72aef2015-03-23 19:13:21 -0700327
Doug Zongkerd5131602012-08-02 14:46:42 -0700328def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700329 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700330 'sourcedir'), and turn them into a boot image. Return the image
331 data, or None if sourcedir does not appear to contains files for
332 building the requested image."""
333
334 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
335 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
336 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700337
Doug Zongkerd5131602012-08-02 14:46:42 -0700338 if info_dict is None:
339 info_dict = OPTIONS.info_dict
340
Doug Zongkereef39442009-04-02 12:14:19 -0700341 ramdisk_img = tempfile.NamedTemporaryFile()
342 img = tempfile.NamedTemporaryFile()
343
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700344 if os.access(fs_config_file, os.F_OK):
345 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
346 else:
347 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
348 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700349 p2 = Run(["minigzip"],
350 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700351
352 p2.wait()
353 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700354 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
355 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700356
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800357 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
358 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
359
360 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700361
Benoit Fradina45a8682014-07-14 21:00:43 +0200362 fn = os.path.join(sourcedir, "second")
363 if os.access(fn, os.F_OK):
364 cmd.append("--second")
365 cmd.append(fn)
366
Doug Zongker171f1cd2009-06-15 22:36:37 -0700367 fn = os.path.join(sourcedir, "cmdline")
368 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700369 cmd.append("--cmdline")
370 cmd.append(open(fn).read().rstrip("\n"))
371
372 fn = os.path.join(sourcedir, "base")
373 if os.access(fn, os.F_OK):
374 cmd.append("--base")
375 cmd.append(open(fn).read().rstrip("\n"))
376
Ying Wang4de6b5b2010-08-25 14:29:34 -0700377 fn = os.path.join(sourcedir, "pagesize")
378 if os.access(fn, os.F_OK):
379 cmd.append("--pagesize")
380 cmd.append(open(fn).read().rstrip("\n"))
381
Doug Zongkerd5131602012-08-02 14:46:42 -0700382 args = info_dict.get("mkbootimg_args", None)
383 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700384 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700385
Tao Baod95e9fd2015-03-29 23:07:41 -0700386 img_unsigned = None
387 if info_dict.get("vboot", None):
388 img_unsigned = tempfile.NamedTemporaryFile()
389 cmd.extend(["--ramdisk", ramdisk_img.name,
390 "--output", img_unsigned.name])
391 else:
392 cmd.extend(["--ramdisk", ramdisk_img.name,
393 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700394
395 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700396 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700397 assert p.returncode == 0, "mkbootimg of %s image failed" % (
398 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700399
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700400 if info_dict.get("verity_key", None):
401 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin852a5b52014-11-20 09:52:05 -0800402 cmd = [OPTIONS.boot_signer_path, path, img.name,
403 info_dict["verity_key"] + ".pk8",
Dan Albert8b72aef2015-03-23 19:13:21 -0700404 info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700405 p = Run(cmd, stdout=subprocess.PIPE)
406 p.communicate()
407 assert p.returncode == 0, "boot_signer of %s image failed" % path
408
Tao Baod95e9fd2015-03-29 23:07:41 -0700409 # Sign the image if vboot is non-empty.
410 elif info_dict.get("vboot", None):
411 path = "/" + os.path.basename(sourcedir).lower()
412 img_keyblock = tempfile.NamedTemporaryFile()
413 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
414 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
415 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
416 img.name]
417 p = Run(cmd, stdout=subprocess.PIPE)
418 p.communicate()
419 assert p.returncode == 0, "vboot_signer of %s image failed" % path
420
Tao Bao2ed665a2015-04-01 11:21:55 -0700421 # Clean up the temp files.
422 img_unsigned.close()
423 img_keyblock.close()
424
Doug Zongkereef39442009-04-02 12:14:19 -0700425 img.seek(os.SEEK_SET, 0)
426 data = img.read()
427
428 ramdisk_img.close()
429 img.close()
430
431 return data
432
433
Doug Zongkerd5131602012-08-02 14:46:42 -0700434def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
435 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800436 """Return a File object (with name 'name') with the desired bootable
437 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700438 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
439 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800440 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700441
Doug Zongker55d93282011-01-25 17:03:34 -0800442 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
443 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700444 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800445 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700446
447 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
448 if os.path.exists(prebuilt_path):
449 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
450 return File.FromLocalFile(name, prebuilt_path)
451
452 print "building image from target_files %s..." % (tree_subdir,)
453 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
454 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
455 os.path.join(unpack_dir, fs_config),
456 info_dict)
457 if data:
458 return File(name, data)
459 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800460
Doug Zongkereef39442009-04-02 12:14:19 -0700461
Doug Zongker75f17362009-12-08 13:46:44 -0800462def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800463 """Unzip the given archive into a temporary directory and return the name.
464
465 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
466 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
467
468 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
469 main file), open for reading.
470 """
Doug Zongkereef39442009-04-02 12:14:19 -0700471
472 tmp = tempfile.mkdtemp(prefix="targetfiles-")
473 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800474
475 def unzip_to_dir(filename, dirname):
476 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
477 if pattern is not None:
478 cmd.append(pattern)
479 p = Run(cmd, stdout=subprocess.PIPE)
480 p.communicate()
481 if p.returncode != 0:
482 raise ExternalError("failed to unzip input target-files \"%s\"" %
483 (filename,))
484
485 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
486 if m:
487 unzip_to_dir(m.group(1), tmp)
488 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
489 filename = m.group(1)
490 else:
491 unzip_to_dir(filename, tmp)
492
493 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700494
495
496def GetKeyPasswords(keylist):
497 """Given a list of keys, prompt the user to enter passwords for
498 those which require them. Return a {key: password} dict. password
499 will be None if the key has no password."""
500
Doug Zongker8ce7c252009-05-22 13:34:54 -0700501 no_passwords = []
502 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700503 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700504 devnull = open("/dev/null", "w+b")
505 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800506 # We don't need a password for things that aren't really keys.
507 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700508 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700509 continue
510
T.R. Fullhart37e10522013-03-18 10:31:26 -0700511 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700512 "-inform", "DER", "-nocrypt"],
513 stdin=devnull.fileno(),
514 stdout=devnull.fileno(),
515 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700516 p.communicate()
517 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700518 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700519 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700520 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700521 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
522 "-inform", "DER", "-passin", "pass:"],
523 stdin=devnull.fileno(),
524 stdout=devnull.fileno(),
525 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700526 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700527 if p.returncode == 0:
528 # Encrypted key with empty string as password.
529 key_passwords[k] = ''
530 elif stderr.startswith('Error decrypting key'):
531 # Definitely encrypted key.
532 # It would have said "Error reading key" if it didn't parse correctly.
533 need_passwords.append(k)
534 else:
535 # Potentially, a type of key that openssl doesn't understand.
536 # We'll let the routines in signapk.jar handle it.
537 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700538 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700539
T.R. Fullhart37e10522013-03-18 10:31:26 -0700540 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700541 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700542 return key_passwords
543
544
Doug Zongker951495f2009-08-14 12:44:19 -0700545def SignFile(input_name, output_name, key, password, align=None,
546 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700547 """Sign the input_name zip/jar/apk, producing output_name. Use the
548 given key and password (the latter may be None if the key does not
549 have a password.
550
551 If align is an integer > 1, zipalign is run to align stored files in
552 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700553
554 If whole_file is true, use the "-w" option to SignApk to embed a
555 signature that covers the whole file in the archive comment of the
556 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700557 """
Doug Zongker951495f2009-08-14 12:44:19 -0700558
Doug Zongkereef39442009-04-02 12:14:19 -0700559 if align == 0 or align == 1:
560 align = None
561
562 if align:
563 temp = tempfile.NamedTemporaryFile()
564 sign_name = temp.name
565 else:
566 sign_name = output_name
567
Baligh Uddin339ee492014-09-05 11:18:07 -0700568 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700569 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
570 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700571 if whole_file:
572 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700573 cmd.extend([key + OPTIONS.public_key_suffix,
574 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700575 input_name, sign_name])
576
577 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700578 if password is not None:
579 password += "\n"
580 p.communicate(password)
581 if p.returncode != 0:
582 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
583
584 if align:
Brian Carlstrom663127d2015-05-22 15:51:19 -0700585 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700586 p.communicate()
587 if p.returncode != 0:
588 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
589 temp.close()
590
591
Doug Zongker37974732010-09-16 17:44:38 -0700592def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700593 """Check the data string passed against the max size limit, if
594 any, for the given target. Raise exception if the data is too big.
595 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700596
Dan Albert8b72aef2015-03-23 19:13:21 -0700597 if target.endswith(".img"):
598 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700599 mount_point = "/" + target
600
Ying Wangf8824af2014-06-03 14:07:27 -0700601 fs_type = None
602 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700603 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700604 if mount_point == "/userdata":
605 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700606 p = info_dict["fstab"][mount_point]
607 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800608 device = p.device
609 if "/" in device:
610 device = device[device.rfind("/")+1:]
611 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700612 if not fs_type or not limit:
613 return
Doug Zongkereef39442009-04-02 12:14:19 -0700614
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700615 if fs_type == "yaffs2":
616 # image size should be increased by 1/64th to account for the
617 # spare area (64 bytes per 2k page)
618 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800619 size = len(data)
620 pct = float(size) * 100.0 / limit
621 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
622 if pct >= 99.0:
623 raise ExternalError(msg)
624 elif pct >= 95.0:
625 print
626 print " WARNING: ", msg
627 print
628 elif OPTIONS.verbose:
629 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700630
631
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800632def ReadApkCerts(tf_zip):
633 """Given a target_files ZipFile, parse the META/apkcerts.txt file
634 and return a {package: cert} dict."""
635 certmap = {}
636 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
637 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700638 if not line:
639 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800640 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
641 r'private_key="(.*)"$', line)
642 if m:
643 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700644 public_key_suffix_len = len(OPTIONS.public_key_suffix)
645 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800646 if cert in SPECIAL_CERT_STRINGS and not privkey:
647 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700648 elif (cert.endswith(OPTIONS.public_key_suffix) and
649 privkey.endswith(OPTIONS.private_key_suffix) and
650 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
651 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800652 else:
653 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
654 return certmap
655
656
Doug Zongkereef39442009-04-02 12:14:19 -0700657COMMON_DOCSTRING = """
658 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700659 Prepend <dir>/bin to the list of places to search for binaries
660 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700661
Doug Zongker05d3dea2009-06-22 11:32:31 -0700662 -s (--device_specific) <file>
663 Path to the python module containing device-specific
664 releasetools code.
665
Doug Zongker8bec09e2009-11-30 15:37:14 -0800666 -x (--extra) <key=value>
667 Add a key/value pair to the 'extras' dict, which device-specific
668 extension code may look at.
669
Doug Zongkereef39442009-04-02 12:14:19 -0700670 -v (--verbose)
671 Show command lines being executed.
672
673 -h (--help)
674 Display this usage message and exit.
675"""
676
677def Usage(docstring):
678 print docstring.rstrip("\n")
679 print COMMON_DOCSTRING
680
681
682def ParseOptions(argv,
683 docstring,
684 extra_opts="", extra_long_opts=(),
685 extra_option_handler=None):
686 """Parse the options in argv and return any arguments that aren't
687 flags. docstring is the calling module's docstring, to be displayed
688 for errors and -h. extra_opts and extra_long_opts are for flags
689 defined by the caller, which are processed by passing them to
690 extra_option_handler."""
691
692 try:
693 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800694 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700695 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700696 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin852a5b52014-11-20 09:52:05 -0800697 "private_key_suffix=", "boot_signer_path=", "device_specific=",
698 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700699 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700700 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700701 Usage(docstring)
702 print "**", str(err), "**"
703 sys.exit(2)
704
Doug Zongkereef39442009-04-02 12:14:19 -0700705 for o, a in opts:
706 if o in ("-h", "--help"):
707 Usage(docstring)
708 sys.exit()
709 elif o in ("-v", "--verbose"):
710 OPTIONS.verbose = True
711 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700712 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700713 elif o in ("--signapk_path",):
714 OPTIONS.signapk_path = a
715 elif o in ("--extra_signapk_args",):
716 OPTIONS.extra_signapk_args = shlex.split(a)
717 elif o in ("--java_path",):
718 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700719 elif o in ("--java_args",):
720 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700721 elif o in ("--public_key_suffix",):
722 OPTIONS.public_key_suffix = a
723 elif o in ("--private_key_suffix",):
724 OPTIONS.private_key_suffix = a
Baligh Uddin852a5b52014-11-20 09:52:05 -0800725 elif o in ("--boot_signer_path",):
726 OPTIONS.boot_signer_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700727 elif o in ("-s", "--device_specific"):
728 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800729 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800730 key, value = a.split("=", 1)
731 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700732 else:
733 if extra_option_handler is None or not extra_option_handler(o, a):
734 assert False, "unknown option \"%s\"" % (o,)
735
Doug Zongker85448772014-09-09 14:59:20 -0700736 if OPTIONS.search_path:
737 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
738 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700739
740 return args
741
742
Doug Zongkerfc44a512014-08-26 13:10:25 -0700743def MakeTempFile(prefix=None, suffix=None):
744 """Make a temp file and add it to the list of things to be deleted
745 when Cleanup() is called. Return the filename."""
746 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
747 os.close(fd)
748 OPTIONS.tempfiles.append(fn)
749 return fn
750
751
Doug Zongkereef39442009-04-02 12:14:19 -0700752def Cleanup():
753 for i in OPTIONS.tempfiles:
754 if os.path.isdir(i):
755 shutil.rmtree(i)
756 else:
757 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700758
759
760class PasswordManager(object):
761 def __init__(self):
762 self.editor = os.getenv("EDITOR", None)
763 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
764
765 def GetPasswords(self, items):
766 """Get passwords corresponding to each string in 'items',
767 returning a dict. (The dict may have keys in addition to the
768 values in 'items'.)
769
770 Uses the passwords in $ANDROID_PW_FILE if available, letting the
771 user edit that file to add more needed passwords. If no editor is
772 available, or $ANDROID_PW_FILE isn't define, prompts the user
773 interactively in the ordinary way.
774 """
775
776 current = self.ReadFile()
777
778 first = True
779 while True:
780 missing = []
781 for i in items:
782 if i not in current or not current[i]:
783 missing.append(i)
784 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700785 if not missing:
786 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700787
788 for i in missing:
789 current[i] = ""
790
791 if not first:
792 print "key file %s still missing some passwords." % (self.pwfile,)
793 answer = raw_input("try to edit again? [y]> ").strip()
794 if answer and answer[0] not in 'yY':
795 raise RuntimeError("key passwords unavailable")
796 first = False
797
798 current = self.UpdateAndReadFile(current)
799
Dan Albert8b72aef2015-03-23 19:13:21 -0700800 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700801 """Prompt the user to enter a value (password) for each key in
802 'current' whose value is fales. Returns a new dict with all the
803 values.
804 """
805 result = {}
806 for k, v in sorted(current.iteritems()):
807 if v:
808 result[k] = v
809 else:
810 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700811 result[k] = getpass.getpass(
812 "Enter password for %s key> " % k).strip()
813 if result[k]:
814 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700815 return result
816
817 def UpdateAndReadFile(self, current):
818 if not self.editor or not self.pwfile:
819 return self.PromptResult(current)
820
821 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700822 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700823 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
824 f.write("# (Additional spaces are harmless.)\n\n")
825
826 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700827 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
828 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700829 f.write("[[[ %s ]]] %s\n" % (v, k))
830 if not v and first_line is None:
831 # position cursor on first line with no password.
832 first_line = i + 4
833 f.close()
834
835 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
836 _, _ = p.communicate()
837
838 return self.ReadFile()
839
840 def ReadFile(self):
841 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700842 if self.pwfile is None:
843 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700844 try:
845 f = open(self.pwfile, "r")
846 for line in f:
847 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700848 if not line or line[0] == '#':
849 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700850 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
851 if not m:
852 print "failed to parse password file: ", line
853 else:
854 result[m.group(2)] = m.group(1)
855 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700856 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700857 if e.errno != errno.ENOENT:
858 print "error reading password file: ", str(e)
859 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700860
861
Dan Albert8e0178d2015-01-27 15:53:15 -0800862def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
863 compress_type=None):
864 import datetime
865
866 # http://b/18015246
867 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
868 # for files larger than 2GiB. We can work around this by adjusting their
869 # limit. Note that `zipfile.writestr()` will not work for strings larger than
870 # 2GiB. The Python interpreter sometimes rejects strings that large (though
871 # it isn't clear to me exactly what circumstances cause this).
872 # `zipfile.write()` must be used directly to work around this.
873 #
874 # This mess can be avoided if we port to python3.
875 saved_zip64_limit = zipfile.ZIP64_LIMIT
876 zipfile.ZIP64_LIMIT = (1 << 32) - 1
877
878 if compress_type is None:
879 compress_type = zip_file.compression
880 if arcname is None:
881 arcname = filename
882
883 saved_stat = os.stat(filename)
884
885 try:
886 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
887 # file to be zipped and reset it when we're done.
888 os.chmod(filename, perms)
889
890 # Use a fixed timestamp so the output is repeatable.
891 epoch = datetime.datetime.fromtimestamp(0)
892 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
893 os.utime(filename, (timestamp, timestamp))
894
895 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
896 finally:
897 os.chmod(filename, saved_stat.st_mode)
898 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
899 zipfile.ZIP64_LIMIT = saved_zip64_limit
900
901
Tao Bao97734652015-05-20 09:32:18 -0700902def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Bao2ed665a2015-04-01 11:21:55 -0700903 compress_type=None):
904 """Wrap zipfile.writestr() function to work around the zip64 limit.
905
906 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
907 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
908 when calling crc32(bytes).
909
910 But it still works fine to write a shorter string into a large zip file.
911 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
912 when we know the string won't be too long.
913 """
914
915 saved_zip64_limit = zipfile.ZIP64_LIMIT
916 zipfile.ZIP64_LIMIT = (1 << 32) - 1
917
918 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
919 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700920 zinfo.compress_type = zip_file.compression
Tao Bao97734652015-05-20 09:32:18 -0700921 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700922 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800923 else:
Tao Bao2ed665a2015-04-01 11:21:55 -0700924 zinfo = zinfo_or_arcname
925
926 # If compress_type is given, it overrides the value in zinfo.
927 if compress_type is not None:
928 zinfo.compress_type = compress_type
929
Tao Bao97734652015-05-20 09:32:18 -0700930 # If perms is given, it has a priority.
931 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700932 # If perms doesn't set the file type, mark it as a regular file.
933 if perms & 0o770000 == 0:
934 perms |= 0o100000
Tao Bao97734652015-05-20 09:32:18 -0700935 zinfo.external_attr = perms << 16
936
Tao Bao2ed665a2015-04-01 11:21:55 -0700937 # Use a fixed timestamp so the output is repeatable.
Tao Bao2ed665a2015-04-01 11:21:55 -0700938 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
939
Dan Albert8b72aef2015-03-23 19:13:21 -0700940 zip_file.writestr(zinfo, data)
Tao Bao2ed665a2015-04-01 11:21:55 -0700941 zipfile.ZIP64_LIMIT = saved_zip64_limit
942
943
944def ZipClose(zip_file):
945 # http://b/18015246
946 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
947 # central directory.
948 saved_zip64_limit = zipfile.ZIP64_LIMIT
949 zipfile.ZIP64_LIMIT = (1 << 32) - 1
950
951 zip_file.close()
952
953 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700954
955
956class DeviceSpecificParams(object):
957 module = None
958 def __init__(self, **kwargs):
959 """Keyword arguments to the constructor become attributes of this
960 object, which is passed to all functions in the device-specific
961 module."""
962 for k, v in kwargs.iteritems():
963 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800964 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700965
966 if self.module is None:
967 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700968 if not path:
969 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700970 try:
971 if os.path.isdir(path):
972 info = imp.find_module("releasetools", [path])
973 else:
974 d, f = os.path.split(path)
975 b, x = os.path.splitext(f)
976 if x == ".py":
977 f = b
978 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800979 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700980 self.module = imp.load_module("device_specific", *info)
981 except ImportError:
982 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700983
984 def _DoCall(self, function_name, *args, **kwargs):
985 """Call the named function in the device-specific module, passing
986 the given args and kwargs. The first argument to the call will be
987 the DeviceSpecific object itself. If there is no module, or the
988 module does not define the function, return the value of the
989 'default' kwarg (which itself defaults to None)."""
990 if self.module is None or not hasattr(self.module, function_name):
991 return kwargs.get("default", None)
992 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
993
994 def FullOTA_Assertions(self):
995 """Called after emitting the block of assertions at the top of a
996 full OTA package. Implementations can add whatever additional
997 assertions they like."""
998 return self._DoCall("FullOTA_Assertions")
999
Doug Zongkere5ff5902012-01-17 10:55:37 -08001000 def FullOTA_InstallBegin(self):
1001 """Called at the start of full OTA installation."""
1002 return self._DoCall("FullOTA_InstallBegin")
1003
Doug Zongker05d3dea2009-06-22 11:32:31 -07001004 def FullOTA_InstallEnd(self):
1005 """Called at the end of full OTA installation; typically this is
1006 used to install the image for the device's baseband processor."""
1007 return self._DoCall("FullOTA_InstallEnd")
1008
1009 def IncrementalOTA_Assertions(self):
1010 """Called after emitting the block of assertions at the top of an
1011 incremental OTA package. Implementations can add whatever
1012 additional assertions they like."""
1013 return self._DoCall("IncrementalOTA_Assertions")
1014
Doug Zongkere5ff5902012-01-17 10:55:37 -08001015 def IncrementalOTA_VerifyBegin(self):
1016 """Called at the start of the verification phase of incremental
1017 OTA installation; additional checks can be placed here to abort
1018 the script before any changes are made."""
1019 return self._DoCall("IncrementalOTA_VerifyBegin")
1020
Doug Zongker05d3dea2009-06-22 11:32:31 -07001021 def IncrementalOTA_VerifyEnd(self):
1022 """Called at the end of the verification phase of incremental OTA
1023 installation; additional checks can be placed here to abort the
1024 script before any changes are made."""
1025 return self._DoCall("IncrementalOTA_VerifyEnd")
1026
Doug Zongkere5ff5902012-01-17 10:55:37 -08001027 def IncrementalOTA_InstallBegin(self):
1028 """Called at the start of incremental OTA installation (after
1029 verification is complete)."""
1030 return self._DoCall("IncrementalOTA_InstallBegin")
1031
Doug Zongker05d3dea2009-06-22 11:32:31 -07001032 def IncrementalOTA_InstallEnd(self):
1033 """Called at the end of incremental OTA installation; typically
1034 this is used to install the image for the device's baseband
1035 processor."""
1036 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001037
1038class File(object):
1039 def __init__(self, name, data):
1040 self.name = name
1041 self.data = data
1042 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001043 self.sha1 = sha1(data).hexdigest()
1044
1045 @classmethod
1046 def FromLocalFile(cls, name, diskname):
1047 f = open(diskname, "rb")
1048 data = f.read()
1049 f.close()
1050 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001051
1052 def WriteToTemp(self):
1053 t = tempfile.NamedTemporaryFile()
1054 t.write(self.data)
1055 t.flush()
1056 return t
1057
Geremy Condra36bd3652014-02-06 19:45:10 -08001058 def AddToZip(self, z, compression=None):
Tao Bao2ed665a2015-04-01 11:21:55 -07001059 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001060
1061DIFF_PROGRAM_BY_EXT = {
1062 ".gz" : "imgdiff",
1063 ".zip" : ["imgdiff", "-z"],
1064 ".jar" : ["imgdiff", "-z"],
1065 ".apk" : ["imgdiff", "-z"],
1066 ".img" : "imgdiff",
1067 }
1068
1069class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001070 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001071 self.tf = tf
1072 self.sf = sf
1073 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001074 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001075
1076 def ComputePatch(self):
1077 """Compute the patch (as a string of data) needed to turn sf into
1078 tf. Returns the same tuple as GetPatch()."""
1079
1080 tf = self.tf
1081 sf = self.sf
1082
Doug Zongker24cd2802012-08-14 16:36:15 -07001083 if self.diff_program:
1084 diff_program = self.diff_program
1085 else:
1086 ext = os.path.splitext(tf.name)[1]
1087 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001088
1089 ttemp = tf.WriteToTemp()
1090 stemp = sf.WriteToTemp()
1091
1092 ext = os.path.splitext(tf.name)[1]
1093
1094 try:
1095 ptemp = tempfile.NamedTemporaryFile()
1096 if isinstance(diff_program, list):
1097 cmd = copy.copy(diff_program)
1098 else:
1099 cmd = [diff_program]
1100 cmd.append(stemp.name)
1101 cmd.append(ttemp.name)
1102 cmd.append(ptemp.name)
1103 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001104 err = []
1105 def run():
1106 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001107 if e:
1108 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001109 th = threading.Thread(target=run)
1110 th.start()
1111 th.join(timeout=300) # 5 mins
1112 if th.is_alive():
1113 print "WARNING: diff command timed out"
1114 p.terminate()
1115 th.join(5)
1116 if th.is_alive():
1117 p.kill()
1118 th.join()
1119
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001120 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001121 print "WARNING: failure running %s:\n%s\n" % (
1122 diff_program, "".join(err))
1123 self.patch = None
1124 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001125 diff = ptemp.read()
1126 finally:
1127 ptemp.close()
1128 stemp.close()
1129 ttemp.close()
1130
1131 self.patch = diff
1132 return self.tf, self.sf, self.patch
1133
1134
1135 def GetPatch(self):
1136 """Return a tuple (target_file, source_file, patch_data).
1137 patch_data may be None if ComputePatch hasn't been called, or if
1138 computing the patch failed."""
1139 return self.tf, self.sf, self.patch
1140
1141
1142def ComputeDifferences(diffs):
1143 """Call ComputePatch on all the Difference objects in 'diffs'."""
1144 print len(diffs), "diffs to compute"
1145
1146 # Do the largest files first, to try and reduce the long-pole effect.
1147 by_size = [(i.tf.size, i) for i in diffs]
1148 by_size.sort(reverse=True)
1149 by_size = [i[1] for i in by_size]
1150
1151 lock = threading.Lock()
1152 diff_iter = iter(by_size) # accessed under lock
1153
1154 def worker():
1155 try:
1156 lock.acquire()
1157 for d in diff_iter:
1158 lock.release()
1159 start = time.time()
1160 d.ComputePatch()
1161 dur = time.time() - start
1162 lock.acquire()
1163
1164 tf, sf, patch = d.GetPatch()
1165 if sf.name == tf.name:
1166 name = tf.name
1167 else:
1168 name = "%s (%s)" % (tf.name, sf.name)
1169 if patch is None:
1170 print "patching failed! %s" % (name,)
1171 else:
1172 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1173 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1174 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001175 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001176 print e
1177 raise
1178
1179 # start worker threads; wait for them all to finish.
1180 threads = [threading.Thread(target=worker)
1181 for i in range(OPTIONS.worker_threads)]
1182 for th in threads:
1183 th.start()
1184 while threads:
1185 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001186
1187
Dan Albert8b72aef2015-03-23 19:13:21 -07001188class BlockDifference(object):
1189 def __init__(self, partition, tgt, src=None, check_first_block=False,
1190 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001191 self.tgt = tgt
1192 self.src = src
1193 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001194 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001195
Tao Baoff777812015-05-12 11:42:31 -07001196 # Due to http://b/20939131, check_first_block is disabled temporarily.
1197 assert not self.check_first_block
1198
Tao Baodd2a5892015-03-12 12:32:37 -07001199 if version is None:
1200 version = 1
1201 if OPTIONS.info_dict:
1202 version = max(
1203 int(i) for i in
1204 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1205 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001206
1207 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001208 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001209 tmpdir = tempfile.mkdtemp()
1210 OPTIONS.tempfiles.append(tmpdir)
1211 self.path = os.path.join(tmpdir, partition)
1212 b.Compute(self.path)
1213
1214 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1215
1216 def WriteScript(self, script, output_zip, progress=None):
1217 if not self.src:
1218 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001219 script.Print("Patching %s image unconditionally..." % (self.partition,))
1220 else:
1221 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001222
Dan Albert8b72aef2015-03-23 19:13:21 -07001223 if progress:
1224 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001225 self._WriteUpdate(script, output_zip)
Tao Bao68658c02015-06-01 13:40:49 -07001226 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001227
1228 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001229 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001230 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001231 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001232 else:
Tao Baoff777812015-05-12 11:42:31 -07001233 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1234 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001235 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001236 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1237 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001238 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001239 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baoff777812015-05-12 11:42:31 -07001240 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001241 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001242 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001243 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Baoff777812015-05-12 11:42:31 -07001244 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001245 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001246 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001247
Tao Baodd2a5892015-03-12 12:32:37 -07001248 # When generating incrementals for the system and vendor partitions,
1249 # explicitly check the first block (which contains the superblock) of
1250 # the partition to see if it's what we expect. If this check fails,
1251 # give an explicit log message about the partition having been
1252 # remounted R/W (the most likely explanation) and the need to flash to
1253 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001254 if self.check_first_block:
1255 self._CheckFirstBlock(script)
1256
Tao Baodd2a5892015-03-12 12:32:37 -07001257 # Abort the OTA update. Note that the incremental OTA cannot be applied
1258 # even if it may match the checksum of the target partition.
1259 # a) If version < 3, operations like move and erase will make changes
1260 # unconditionally and damage the partition.
1261 # b) If version >= 3, it won't even reach here.
1262 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1263 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001264
Tao Bao68658c02015-06-01 13:40:49 -07001265 def _WritePostInstallVerifyScript(self, script):
1266 partition = self.partition
1267 script.Print('Verifying the updated %s image...' % (partition,))
1268 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1269 ranges = self.tgt.care_map
1270 ranges_str = ranges.to_string_raw()
1271 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1272 self.device, ranges_str,
1273 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001274
1275 # Bug: 20881595
1276 # Verify that extended blocks are really zeroed out.
1277 if self.tgt.extended:
1278 ranges_str = self.tgt.extended.to_string_raw()
1279 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1280 self.device, ranges_str,
1281 self._HashZeroBlocks(self.tgt.extended.size())))
1282 script.Print('Verified the updated %s image.' % (partition,))
1283 script.AppendExtra(
1284 'else\n'
1285 ' abort("%s partition has unexpected non-zero contents after OTA '
1286 'update");\n'
1287 'endif;' % (partition,))
1288 else:
1289 script.Print('Verified the updated %s image.' % (partition,))
1290
Tao Bao68658c02015-06-01 13:40:49 -07001291 script.AppendExtra(
1292 'else\n'
1293 ' abort("%s partition has unexpected contents after OTA update");\n'
1294 'endif;' % (partition,))
1295
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001296 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001297 ZipWrite(output_zip,
1298 '{}.transfer.list'.format(self.path),
1299 '{}.transfer.list'.format(self.partition))
1300 ZipWrite(output_zip,
1301 '{}.new.dat'.format(self.path),
1302 '{}.new.dat'.format(self.partition))
1303 ZipWrite(output_zip,
1304 '{}.patch.dat'.format(self.path),
1305 '{}.patch.dat'.format(self.partition),
1306 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001307
Dan Albert8e0178d2015-01-27 15:53:15 -08001308 call = ('block_image_update("{device}", '
1309 'package_extract_file("{partition}.transfer.list"), '
1310 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1311 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001312 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001313
Dan Albert8b72aef2015-03-23 19:13:21 -07001314 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001315 data = source.ReadRangeSet(ranges)
1316 ctx = sha1()
1317
1318 for p in data:
1319 ctx.update(p)
1320
1321 return ctx.hexdigest()
1322
Tao Baoe9b61912015-07-09 17:37:49 -07001323 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1324 """Return the hash value for all zero blocks."""
1325 zero_block = '\x00' * 4096
1326 ctx = sha1()
1327 for _ in range(num_blocks):
1328 ctx.update(zero_block)
1329
1330 return ctx.hexdigest()
1331
Tao Baoff777812015-05-12 11:42:31 -07001332 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1333 # remounting R/W. Will change the checking to a finer-grained way to
1334 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001335 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001336 r = rangelib.RangeSet((0, 1))
1337 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001338
1339 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1340 'abort("%s has been remounted R/W; '
1341 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001342 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001343 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001344
1345DataImage = blockimgdiff.DataImage
1346
1347
Doug Zongker96a57e72010-09-26 14:57:41 -07001348# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001349PARTITION_TYPES = {
1350 "yaffs2": "MTD",
1351 "mtd": "MTD",
1352 "ext4": "EMMC",
1353 "emmc": "EMMC",
Mohamad Ayyasha9905342015-05-01 15:39:36 -07001354 "f2fs": "EMMC",
1355 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001356}
Doug Zongker96a57e72010-09-26 14:57:41 -07001357
1358def GetTypeAndDevice(mount_point, info):
1359 fstab = info["fstab"]
1360 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001361 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1362 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001363 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001364 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001365
1366
1367def ParseCertificate(data):
1368 """Parse a PEM-format certificate."""
1369 cert = []
1370 save = False
1371 for line in data.split("\n"):
1372 if "--END CERTIFICATE--" in line:
1373 break
1374 if save:
1375 cert.append(line)
1376 if "--BEGIN CERTIFICATE--" in line:
1377 save = True
1378 cert = "".join(cert).decode('base64')
1379 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001380
Doug Zongker412c02f2014-02-13 10:58:24 -08001381def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1382 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001383 """Generate a binary patch that creates the recovery image starting
1384 with the boot image. (Most of the space in these images is just the
1385 kernel, which is identical for the two, so the resulting patch
1386 should be efficient.) Add it to the output zip, along with a shell
1387 script that is run from init.rc on first boot to actually do the
1388 patching and install the new recovery image.
1389
1390 recovery_img and boot_img should be File objects for the
1391 corresponding images. info should be the dictionary returned by
1392 common.LoadInfoDict() on the input target_files.
1393 """
1394
Doug Zongker412c02f2014-02-13 10:58:24 -08001395 if info_dict is None:
1396 info_dict = OPTIONS.info_dict
1397
Doug Zongkerc9253822014-02-04 12:17:58 -08001398 diff_program = ["imgdiff"]
1399 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1400 if os.path.exists(path):
1401 diff_program.append("-b")
1402 diff_program.append(path)
1403 bonus_args = "-b /system/etc/recovery-resource.dat"
1404 else:
1405 bonus_args = ""
1406
1407 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1408 _, _, patch = d.ComputePatch()
1409 output_sink("recovery-from-boot.p", patch)
1410
Dan Albertebb19aa2015-03-27 19:11:53 -07001411 try:
1412 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1413 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1414 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001415 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001416
1417 sh = """#!/system/bin/sh
1418if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1419 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"
1420else
1421 log -t recovery "Recovery image already installed"
1422fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001423""" % {'boot_size': boot_img.size,
1424 'boot_sha1': boot_img.sha1,
1425 'recovery_size': recovery_img.size,
1426 'recovery_sha1': recovery_img.sha1,
1427 'boot_type': boot_type,
1428 'boot_device': boot_device,
1429 'recovery_type': recovery_type,
1430 'recovery_device': recovery_device,
1431 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001432
1433 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001434 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001435 # target-files expects it to be, and put it there.
1436 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001437 found = False
1438 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
1439 init_rc_files = os.listdir(init_rc_dir)
1440 for init_rc_file in init_rc_files:
1441 if (not init_rc_file.startswith('init.') or
1442 not init_rc_file.endswith('.rc')):
1443 continue
1444
1445 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001446 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001447 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001448 if m:
1449 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001450 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001451 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001452
1453 if found:
1454 break
1455
1456 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001457
1458 output_sink(sh_location, sh)