blob: 584dc67a2c86613c6474ef25d7c38323872e3d19 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
Dan Albert8b72aef2015-03-23 19:13:21 -070033import rangelib
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Tao Baof3282b42015-04-01 11:21:55 -070035from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036
Doug Zongkereef39442009-04-02 12:14:19 -070037
Dan Albert8b72aef2015-03-23 19:13:21 -070038class Options(object):
39 def __init__(self):
40 platform_search_path = {
41 "linux2": "out/host/linux-x86",
42 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070043 }
Doug Zongker85448772014-09-09 14:59:20 -070044
Dan Albert8b72aef2015-03-23 19:13:21 -070045 self.search_path = platform_search_path.get(sys.platform, None)
46 self.signapk_path = "framework/signapk.jar" # Relative to search_path
47 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
49 self.java_args = "-Xmx2048m" # JVM Args
50 self.public_key_suffix = ".x509.pem"
51 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070054 self.boot_signer_args = []
55 self.verity_signer_path = None
56 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070057 self.verbose = False
58 self.tempfiles = []
59 self.device_specific = None
60 self.extras = {}
61 self.info_dict = None
Tao Baoe09359a2015-10-13 16:37:12 -070062 self.source_info_dict = None
63 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070064 self.worker_threads = None
Tao Bao116977c2015-08-07 19:49:45 -070065 # Stash size cannot exceed cache_size * threshold.
66 self.cache_size = None
67 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070068
69
70OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070071
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080072
73# Values for "certificate" in apkcerts that mean special things.
74SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
75
76
Dan Albert8b72aef2015-03-23 19:13:21 -070077class ExternalError(RuntimeError):
78 pass
Doug Zongkereef39442009-04-02 12:14:19 -070079
80
81def Run(args, **kwargs):
82 """Create and return a subprocess.Popen object, printing the command
83 line on the terminal if -v was specified."""
84 if OPTIONS.verbose:
85 print " running: ", " ".join(args)
86 return subprocess.Popen(args, **kwargs)
87
88
Ying Wang7e6d4e42010-12-13 16:25:36 -080089def CloseInheritedPipes():
90 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
91 before doing other work."""
92 if platform.system() != "Darwin":
93 return
94 for d in range(3, 1025):
95 try:
96 stat = os.fstat(d)
97 if stat is not None:
98 pipebit = stat[0] & 0x1000
99 if pipebit != 0:
100 os.close(d)
101 except OSError:
102 pass
103
104
Dan Albert8b72aef2015-03-23 19:13:21 -0700105def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700106 """Read and parse the META/misc_info.txt key/value pairs from the
107 input target files and return a dict."""
108
Doug Zongkerc9253822014-02-04 12:17:58 -0800109 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700110 if isinstance(input_file, zipfile.ZipFile):
111 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800112 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700113 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800114 try:
115 with open(path) as f:
116 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700117 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800118 if e.errno == errno.ENOENT:
119 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700120 d = {}
121 try:
Michael Runge6e836112014-04-15 17:40:21 -0700122 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700123 except KeyError:
124 # ok if misc_info.txt doesn't exist
125 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700126
Doug Zongker37974732010-09-16 17:44:38 -0700127 # backwards compatibility: These values used to be in their own
128 # files. Look for them, in case we're processing an old
129 # target_files zip.
130
131 if "mkyaffs2_extra_flags" not in d:
132 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700133 d["mkyaffs2_extra_flags"] = read_helper(
134 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700135 except KeyError:
136 # ok if flags don't exist
137 pass
138
139 if "recovery_api_version" not in d:
140 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700141 d["recovery_api_version"] = read_helper(
142 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700143 except KeyError:
144 raise ValueError("can't find recovery API version in input target-files")
145
146 if "tool_extensions" not in d:
147 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800148 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700149 except KeyError:
150 # ok if extensions don't exist
151 pass
152
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800153 if "fstab_version" not in d:
154 d["fstab_version"] = "1"
155
Doug Zongker37974732010-09-16 17:44:38 -0700156 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800157 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700158 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700159 if not line:
160 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700161 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700162 if not value:
163 continue
Doug Zongker37974732010-09-16 17:44:38 -0700164 if name == "blocksize":
165 d[name] = value
166 else:
167 d[name + "_size"] = value
168 except KeyError:
169 pass
170
171 def makeint(key):
172 if key in d:
173 d[key] = int(d[key], 0)
174
175 makeint("recovery_api_version")
176 makeint("blocksize")
177 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700178 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700179 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700180 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700181 makeint("recovery_size")
182 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800183 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700184
Daniel Rosenbergb3b8ce62015-06-05 17:59:27 -0700185 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"], d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800186 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700187 return d
188
Doug Zongkerc9253822014-02-04 12:17:58 -0800189def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700190 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800191 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700192 except KeyError:
193 print "Warning: could not find SYSTEM/build.prop in %s" % zip
194 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700195 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700196
Michael Runge6e836112014-04-15 17:40:21 -0700197def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700198 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700199 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700200 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700201 if not line or line.startswith("#"):
202 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700203 if "=" in line:
204 name, value = line.split("=", 1)
205 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700206 return d
207
Daniel Rosenbergb3b8ce62015-06-05 17:59:27 -0700208def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700209 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700210 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700211 self.mount_point = mount_point
212 self.fs_type = fs_type
213 self.device = device
214 self.length = length
215 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700216 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700217
218 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800219 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700220 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800221 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700222 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700223
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800224 if fstab_version == 1:
225 d = {}
226 for line in data.split("\n"):
227 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700228 if not line or line.startswith("#"):
229 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800230 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800232 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800233 options = None
234 if len(pieces) >= 4:
235 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700236 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800237 if len(pieces) >= 5:
238 options = pieces[4]
239 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700240 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800241 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800242 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700243 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700244
Dan Albert8b72aef2015-03-23 19:13:21 -0700245 mount_point = pieces[0]
246 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800247 if options:
248 options = options.split(",")
249 for i in options:
250 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700251 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800252 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700253 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800254
Dan Albert8b72aef2015-03-23 19:13:21 -0700255 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
256 device=pieces[2], length=length,
257 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800258
259 elif fstab_version == 2:
260 d = {}
261 for line in data.split("\n"):
262 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700263 if not line or line.startswith("#"):
264 continue
Tao Bao548eb762015-06-10 12:32:41 -0700265 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800266 pieces = line.split()
267 if len(pieces) != 5:
268 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
269
270 # Ignore entries that are managed by vold
271 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700272 if "voldmanaged=" in options:
273 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800274
275 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700276 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800277 options = options.split(",")
278 for i in options:
279 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700280 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800281 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800282 # Ignore all unknown options in the unified fstab
283 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800284
Tao Bao548eb762015-06-10 12:32:41 -0700285 mount_flags = pieces[3]
286 # Honor the SELinux context if present.
287 context = None
288 for i in mount_flags.split(","):
289 if i.startswith("context="):
290 context = i
291
Dan Albert8b72aef2015-03-23 19:13:21 -0700292 mount_point = pieces[1]
293 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700294 device=pieces[0], length=length,
295 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800296
297 else:
298 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
299
Daniel Rosenbergb3b8ce62015-06-05 17:59:27 -0700300 # / is used for the system mount point when the root directory is included in
301 # system. Other areas assume system is always at "/system" so point /system at /
302 if system_root_image:
303 assert not d.has_key("/system") and d.has_key("/")
304 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700305 return d
306
307
Doug Zongker37974732010-09-16 17:44:38 -0700308def DumpInfoDict(d):
309 for k, v in sorted(d.items()):
310 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700311
Dan Albert8b72aef2015-03-23 19:13:21 -0700312
Doug Zongkerd5131602012-08-02 14:46:42 -0700313def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700314 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700315 'sourcedir'), and turn them into a boot image. Return the image
316 data, or None if sourcedir does not appear to contains files for
317 building the requested image."""
318
319 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
320 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
321 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700322
Doug Zongkerd5131602012-08-02 14:46:42 -0700323 if info_dict is None:
324 info_dict = OPTIONS.info_dict
325
Doug Zongkereef39442009-04-02 12:14:19 -0700326 ramdisk_img = tempfile.NamedTemporaryFile()
327 img = tempfile.NamedTemporaryFile()
328
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700329 if os.access(fs_config_file, os.F_OK):
330 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
331 else:
332 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
333 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700334 p2 = Run(["minigzip"],
335 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700336
337 p2.wait()
338 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700339 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
340 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700341
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800342 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
343 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
344
345 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700346
Benoit Fradina45a8682014-07-14 21:00:43 +0200347 fn = os.path.join(sourcedir, "second")
348 if os.access(fn, os.F_OK):
349 cmd.append("--second")
350 cmd.append(fn)
351
Doug Zongker171f1cd2009-06-15 22:36:37 -0700352 fn = os.path.join(sourcedir, "cmdline")
353 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700354 cmd.append("--cmdline")
355 cmd.append(open(fn).read().rstrip("\n"))
356
357 fn = os.path.join(sourcedir, "base")
358 if os.access(fn, os.F_OK):
359 cmd.append("--base")
360 cmd.append(open(fn).read().rstrip("\n"))
361
Ying Wang4de6b5b2010-08-25 14:29:34 -0700362 fn = os.path.join(sourcedir, "pagesize")
363 if os.access(fn, os.F_OK):
364 cmd.append("--pagesize")
365 cmd.append(open(fn).read().rstrip("\n"))
366
Doug Zongkerd5131602012-08-02 14:46:42 -0700367 args = info_dict.get("mkbootimg_args", None)
368 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700369 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700370
Tao Baod95e9fd2015-03-29 23:07:41 -0700371 img_unsigned = None
372 if info_dict.get("vboot", None):
373 img_unsigned = tempfile.NamedTemporaryFile()
374 cmd.extend(["--ramdisk", ramdisk_img.name,
375 "--output", img_unsigned.name])
376 else:
377 cmd.extend(["--ramdisk", ramdisk_img.name,
378 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700379
380 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700381 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700382 assert p.returncode == 0, "mkbootimg of %s image failed" % (
383 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700384
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100385 if (info_dict.get("boot_signer", None) == "true" and
386 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700387 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700388 cmd = [OPTIONS.boot_signer_path]
389 cmd.extend(OPTIONS.boot_signer_args)
390 cmd.extend([path, img.name,
391 info_dict["verity_key"] + ".pk8",
392 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700393 p = Run(cmd, stdout=subprocess.PIPE)
394 p.communicate()
395 assert p.returncode == 0, "boot_signer of %s image failed" % path
396
Tao Baod95e9fd2015-03-29 23:07:41 -0700397 # Sign the image if vboot is non-empty.
398 elif info_dict.get("vboot", None):
399 path = "/" + os.path.basename(sourcedir).lower()
400 img_keyblock = tempfile.NamedTemporaryFile()
401 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
402 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700403 info_dict["vboot_key"] + ".vbprivk",
404 info_dict["vboot_subkey"] + ".vbprivk",
405 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700406 img.name]
407 p = Run(cmd, stdout=subprocess.PIPE)
408 p.communicate()
409 assert p.returncode == 0, "vboot_signer of %s image failed" % path
410
Tao Baof3282b42015-04-01 11:21:55 -0700411 # Clean up the temp files.
412 img_unsigned.close()
413 img_keyblock.close()
414
Doug Zongkereef39442009-04-02 12:14:19 -0700415 img.seek(os.SEEK_SET, 0)
416 data = img.read()
417
418 ramdisk_img.close()
419 img.close()
420
421 return data
422
423
Doug Zongkerd5131602012-08-02 14:46:42 -0700424def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
425 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800426 """Return a File object (with name 'name') with the desired bootable
427 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700428 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
429 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800430 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700431
Doug Zongker55d93282011-01-25 17:03:34 -0800432 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
433 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700434 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800435 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700436
437 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
438 if os.path.exists(prebuilt_path):
439 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
440 return File.FromLocalFile(name, prebuilt_path)
441
442 print "building image from target_files %s..." % (tree_subdir,)
443 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
444 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
445 os.path.join(unpack_dir, fs_config),
446 info_dict)
447 if data:
448 return File(name, data)
449 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800450
Doug Zongkereef39442009-04-02 12:14:19 -0700451
Doug Zongker75f17362009-12-08 13:46:44 -0800452def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800453 """Unzip the given archive into a temporary directory and return the name.
454
455 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
456 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
457
458 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
459 main file), open for reading.
460 """
Doug Zongkereef39442009-04-02 12:14:19 -0700461
462 tmp = tempfile.mkdtemp(prefix="targetfiles-")
463 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800464
465 def unzip_to_dir(filename, dirname):
466 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
467 if pattern is not None:
468 cmd.append(pattern)
469 p = Run(cmd, stdout=subprocess.PIPE)
470 p.communicate()
471 if p.returncode != 0:
472 raise ExternalError("failed to unzip input target-files \"%s\"" %
473 (filename,))
474
475 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
476 if m:
477 unzip_to_dir(m.group(1), tmp)
478 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
479 filename = m.group(1)
480 else:
481 unzip_to_dir(filename, tmp)
482
483 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700484
485
486def GetKeyPasswords(keylist):
487 """Given a list of keys, prompt the user to enter passwords for
488 those which require them. Return a {key: password} dict. password
489 will be None if the key has no password."""
490
Doug Zongker8ce7c252009-05-22 13:34:54 -0700491 no_passwords = []
492 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700493 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700494 devnull = open("/dev/null", "w+b")
495 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800496 # We don't need a password for things that aren't really keys.
497 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700498 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700499 continue
500
T.R. Fullhart37e10522013-03-18 10:31:26 -0700501 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700502 "-inform", "DER", "-nocrypt"],
503 stdin=devnull.fileno(),
504 stdout=devnull.fileno(),
505 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700506 p.communicate()
507 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700508 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700509 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700510 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700511 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
512 "-inform", "DER", "-passin", "pass:"],
513 stdin=devnull.fileno(),
514 stdout=devnull.fileno(),
515 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700516 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700517 if p.returncode == 0:
518 # Encrypted key with empty string as password.
519 key_passwords[k] = ''
520 elif stderr.startswith('Error decrypting key'):
521 # Definitely encrypted key.
522 # It would have said "Error reading key" if it didn't parse correctly.
523 need_passwords.append(k)
524 else:
525 # Potentially, a type of key that openssl doesn't understand.
526 # We'll let the routines in signapk.jar handle it.
527 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700528 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700529
T.R. Fullhart37e10522013-03-18 10:31:26 -0700530 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700531 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700532 return key_passwords
533
534
Doug Zongker951495f2009-08-14 12:44:19 -0700535def SignFile(input_name, output_name, key, password, align=None,
536 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700537 """Sign the input_name zip/jar/apk, producing output_name. Use the
538 given key and password (the latter may be None if the key does not
539 have a password.
540
541 If align is an integer > 1, zipalign is run to align stored files in
542 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700543
544 If whole_file is true, use the "-w" option to SignApk to embed a
545 signature that covers the whole file in the archive comment of the
546 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700547 """
Doug Zongker951495f2009-08-14 12:44:19 -0700548
Doug Zongkereef39442009-04-02 12:14:19 -0700549 if align == 0 or align == 1:
550 align = None
551
552 if align:
553 temp = tempfile.NamedTemporaryFile()
554 sign_name = temp.name
555 else:
556 sign_name = output_name
557
Baligh Uddin339ee492014-09-05 11:18:07 -0700558 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700559 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
560 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700561 if whole_file:
562 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700563 cmd.extend([key + OPTIONS.public_key_suffix,
564 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700565 input_name, sign_name])
566
567 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700568 if password is not None:
569 password += "\n"
570 p.communicate(password)
571 if p.returncode != 0:
572 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
573
574 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700575 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700576 p.communicate()
577 if p.returncode != 0:
578 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
579 temp.close()
580
581
Doug Zongker37974732010-09-16 17:44:38 -0700582def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700583 """Check the data string passed against the max size limit, if
584 any, for the given target. Raise exception if the data is too big.
585 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700586
Dan Albert8b72aef2015-03-23 19:13:21 -0700587 if target.endswith(".img"):
588 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700589 mount_point = "/" + target
590
Ying Wangf8824af2014-06-03 14:07:27 -0700591 fs_type = None
592 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700593 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700594 if mount_point == "/userdata":
595 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700596 p = info_dict["fstab"][mount_point]
597 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800598 device = p.device
599 if "/" in device:
600 device = device[device.rfind("/")+1:]
601 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700602 if not fs_type or not limit:
603 return
Doug Zongkereef39442009-04-02 12:14:19 -0700604
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700605 if fs_type == "yaffs2":
606 # image size should be increased by 1/64th to account for the
607 # spare area (64 bytes per 2k page)
608 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800609 size = len(data)
610 pct = float(size) * 100.0 / limit
611 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
612 if pct >= 99.0:
613 raise ExternalError(msg)
614 elif pct >= 95.0:
615 print
616 print " WARNING: ", msg
617 print
618 elif OPTIONS.verbose:
619 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700620
621
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800622def ReadApkCerts(tf_zip):
623 """Given a target_files ZipFile, parse the META/apkcerts.txt file
624 and return a {package: cert} dict."""
625 certmap = {}
626 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
627 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700628 if not line:
629 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800630 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
631 r'private_key="(.*)"$', line)
632 if m:
633 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700634 public_key_suffix_len = len(OPTIONS.public_key_suffix)
635 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800636 if cert in SPECIAL_CERT_STRINGS and not privkey:
637 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700638 elif (cert.endswith(OPTIONS.public_key_suffix) and
639 privkey.endswith(OPTIONS.private_key_suffix) and
640 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
641 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800642 else:
643 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
644 return certmap
645
646
Doug Zongkereef39442009-04-02 12:14:19 -0700647COMMON_DOCSTRING = """
648 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700649 Prepend <dir>/bin to the list of places to search for binaries
650 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700651
Doug Zongker05d3dea2009-06-22 11:32:31 -0700652 -s (--device_specific) <file>
653 Path to the python module containing device-specific
654 releasetools code.
655
Doug Zongker8bec09e2009-11-30 15:37:14 -0800656 -x (--extra) <key=value>
657 Add a key/value pair to the 'extras' dict, which device-specific
658 extension code may look at.
659
Doug Zongkereef39442009-04-02 12:14:19 -0700660 -v (--verbose)
661 Show command lines being executed.
662
663 -h (--help)
664 Display this usage message and exit.
665"""
666
667def Usage(docstring):
668 print docstring.rstrip("\n")
669 print COMMON_DOCSTRING
670
671
672def ParseOptions(argv,
673 docstring,
674 extra_opts="", extra_long_opts=(),
675 extra_option_handler=None):
676 """Parse the options in argv and return any arguments that aren't
677 flags. docstring is the calling module's docstring, to be displayed
678 for errors and -h. extra_opts and extra_long_opts are for flags
679 defined by the caller, which are processed by passing them to
680 extra_option_handler."""
681
682 try:
683 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800684 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700685 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700686 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700687 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
688 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800689 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700690 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700691 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700692 Usage(docstring)
693 print "**", str(err), "**"
694 sys.exit(2)
695
Doug Zongkereef39442009-04-02 12:14:19 -0700696 for o, a in opts:
697 if o in ("-h", "--help"):
698 Usage(docstring)
699 sys.exit()
700 elif o in ("-v", "--verbose"):
701 OPTIONS.verbose = True
702 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700703 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700704 elif o in ("--signapk_path",):
705 OPTIONS.signapk_path = a
706 elif o in ("--extra_signapk_args",):
707 OPTIONS.extra_signapk_args = shlex.split(a)
708 elif o in ("--java_path",):
709 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700710 elif o in ("--java_args",):
711 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700712 elif o in ("--public_key_suffix",):
713 OPTIONS.public_key_suffix = a
714 elif o in ("--private_key_suffix",):
715 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800716 elif o in ("--boot_signer_path",):
717 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700718 elif o in ("--boot_signer_args",):
719 OPTIONS.boot_signer_args = shlex.split(a)
720 elif o in ("--verity_signer_path",):
721 OPTIONS.verity_signer_path = a
722 elif o in ("--verity_signer_args",):
723 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700724 elif o in ("-s", "--device_specific"):
725 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800726 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800727 key, value = a.split("=", 1)
728 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700729 else:
730 if extra_option_handler is None or not extra_option_handler(o, a):
731 assert False, "unknown option \"%s\"" % (o,)
732
Doug Zongker85448772014-09-09 14:59:20 -0700733 if OPTIONS.search_path:
734 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
735 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700736
737 return args
738
739
Doug Zongkerfc44a512014-08-26 13:10:25 -0700740def MakeTempFile(prefix=None, suffix=None):
741 """Make a temp file and add it to the list of things to be deleted
742 when Cleanup() is called. Return the filename."""
743 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
744 os.close(fd)
745 OPTIONS.tempfiles.append(fn)
746 return fn
747
748
Doug Zongkereef39442009-04-02 12:14:19 -0700749def Cleanup():
750 for i in OPTIONS.tempfiles:
751 if os.path.isdir(i):
752 shutil.rmtree(i)
753 else:
754 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700755
756
757class PasswordManager(object):
758 def __init__(self):
759 self.editor = os.getenv("EDITOR", None)
760 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
761
762 def GetPasswords(self, items):
763 """Get passwords corresponding to each string in 'items',
764 returning a dict. (The dict may have keys in addition to the
765 values in 'items'.)
766
767 Uses the passwords in $ANDROID_PW_FILE if available, letting the
768 user edit that file to add more needed passwords. If no editor is
769 available, or $ANDROID_PW_FILE isn't define, prompts the user
770 interactively in the ordinary way.
771 """
772
773 current = self.ReadFile()
774
775 first = True
776 while True:
777 missing = []
778 for i in items:
779 if i not in current or not current[i]:
780 missing.append(i)
781 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700782 if not missing:
783 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700784
785 for i in missing:
786 current[i] = ""
787
788 if not first:
789 print "key file %s still missing some passwords." % (self.pwfile,)
790 answer = raw_input("try to edit again? [y]> ").strip()
791 if answer and answer[0] not in 'yY':
792 raise RuntimeError("key passwords unavailable")
793 first = False
794
795 current = self.UpdateAndReadFile(current)
796
Dan Albert8b72aef2015-03-23 19:13:21 -0700797 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700798 """Prompt the user to enter a value (password) for each key in
799 'current' whose value is fales. Returns a new dict with all the
800 values.
801 """
802 result = {}
803 for k, v in sorted(current.iteritems()):
804 if v:
805 result[k] = v
806 else:
807 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700808 result[k] = getpass.getpass(
809 "Enter password for %s key> " % k).strip()
810 if result[k]:
811 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700812 return result
813
814 def UpdateAndReadFile(self, current):
815 if not self.editor or not self.pwfile:
816 return self.PromptResult(current)
817
818 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700819 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700820 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
821 f.write("# (Additional spaces are harmless.)\n\n")
822
823 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700824 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
825 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700826 f.write("[[[ %s ]]] %s\n" % (v, k))
827 if not v and first_line is None:
828 # position cursor on first line with no password.
829 first_line = i + 4
830 f.close()
831
832 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
833 _, _ = p.communicate()
834
835 return self.ReadFile()
836
837 def ReadFile(self):
838 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700839 if self.pwfile is None:
840 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700841 try:
842 f = open(self.pwfile, "r")
843 for line in f:
844 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700845 if not line or line[0] == '#':
846 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700847 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
848 if not m:
849 print "failed to parse password file: ", line
850 else:
851 result[m.group(2)] = m.group(1)
852 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700853 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700854 if e.errno != errno.ENOENT:
855 print "error reading password file: ", str(e)
856 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700857
858
Dan Albert8e0178d2015-01-27 15:53:15 -0800859def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
860 compress_type=None):
861 import datetime
862
863 # http://b/18015246
864 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
865 # for files larger than 2GiB. We can work around this by adjusting their
866 # limit. Note that `zipfile.writestr()` will not work for strings larger than
867 # 2GiB. The Python interpreter sometimes rejects strings that large (though
868 # it isn't clear to me exactly what circumstances cause this).
869 # `zipfile.write()` must be used directly to work around this.
870 #
871 # This mess can be avoided if we port to python3.
872 saved_zip64_limit = zipfile.ZIP64_LIMIT
873 zipfile.ZIP64_LIMIT = (1 << 32) - 1
874
875 if compress_type is None:
876 compress_type = zip_file.compression
877 if arcname is None:
878 arcname = filename
879
880 saved_stat = os.stat(filename)
881
882 try:
883 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
884 # file to be zipped and reset it when we're done.
885 os.chmod(filename, perms)
886
887 # Use a fixed timestamp so the output is repeatable.
888 epoch = datetime.datetime.fromtimestamp(0)
889 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
890 os.utime(filename, (timestamp, timestamp))
891
892 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
893 finally:
894 os.chmod(filename, saved_stat.st_mode)
895 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
896 zipfile.ZIP64_LIMIT = saved_zip64_limit
897
898
Tao Bao58c1b962015-05-20 09:32:18 -0700899def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700900 compress_type=None):
901 """Wrap zipfile.writestr() function to work around the zip64 limit.
902
903 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
904 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
905 when calling crc32(bytes).
906
907 But it still works fine to write a shorter string into a large zip file.
908 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
909 when we know the string won't be too long.
910 """
911
912 saved_zip64_limit = zipfile.ZIP64_LIMIT
913 zipfile.ZIP64_LIMIT = (1 << 32) - 1
914
915 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
916 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700917 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700918 if perms is None:
919 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800920 else:
Tao Baof3282b42015-04-01 11:21:55 -0700921 zinfo = zinfo_or_arcname
922
923 # If compress_type is given, it overrides the value in zinfo.
924 if compress_type is not None:
925 zinfo.compress_type = compress_type
926
Tao Bao58c1b962015-05-20 09:32:18 -0700927 # If perms is given, it has a priority.
928 if perms is not None:
929 zinfo.external_attr = perms << 16
930
Tao Baof3282b42015-04-01 11:21:55 -0700931 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700932 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
933
Dan Albert8b72aef2015-03-23 19:13:21 -0700934 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700935 zipfile.ZIP64_LIMIT = saved_zip64_limit
936
937
938def ZipClose(zip_file):
939 # http://b/18015246
940 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
941 # central directory.
942 saved_zip64_limit = zipfile.ZIP64_LIMIT
943 zipfile.ZIP64_LIMIT = (1 << 32) - 1
944
945 zip_file.close()
946
947 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700948
949
950class DeviceSpecificParams(object):
951 module = None
952 def __init__(self, **kwargs):
953 """Keyword arguments to the constructor become attributes of this
954 object, which is passed to all functions in the device-specific
955 module."""
956 for k, v in kwargs.iteritems():
957 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800958 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700959
960 if self.module is None:
961 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700962 if not path:
963 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700964 try:
965 if os.path.isdir(path):
966 info = imp.find_module("releasetools", [path])
967 else:
968 d, f = os.path.split(path)
969 b, x = os.path.splitext(f)
970 if x == ".py":
971 f = b
972 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800973 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700974 self.module = imp.load_module("device_specific", *info)
975 except ImportError:
976 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700977
978 def _DoCall(self, function_name, *args, **kwargs):
979 """Call the named function in the device-specific module, passing
980 the given args and kwargs. The first argument to the call will be
981 the DeviceSpecific object itself. If there is no module, or the
982 module does not define the function, return the value of the
983 'default' kwarg (which itself defaults to None)."""
984 if self.module is None or not hasattr(self.module, function_name):
985 return kwargs.get("default", None)
986 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
987
988 def FullOTA_Assertions(self):
989 """Called after emitting the block of assertions at the top of a
990 full OTA package. Implementations can add whatever additional
991 assertions they like."""
992 return self._DoCall("FullOTA_Assertions")
993
Doug Zongkere5ff5902012-01-17 10:55:37 -0800994 def FullOTA_InstallBegin(self):
995 """Called at the start of full OTA installation."""
996 return self._DoCall("FullOTA_InstallBegin")
997
Doug Zongker05d3dea2009-06-22 11:32:31 -0700998 def FullOTA_InstallEnd(self):
999 """Called at the end of full OTA installation; typically this is
1000 used to install the image for the device's baseband processor."""
1001 return self._DoCall("FullOTA_InstallEnd")
1002
1003 def IncrementalOTA_Assertions(self):
1004 """Called after emitting the block of assertions at the top of an
1005 incremental OTA package. Implementations can add whatever
1006 additional assertions they like."""
1007 return self._DoCall("IncrementalOTA_Assertions")
1008
Doug Zongkere5ff5902012-01-17 10:55:37 -08001009 def IncrementalOTA_VerifyBegin(self):
1010 """Called at the start of the verification phase of incremental
1011 OTA installation; additional checks can be placed here to abort
1012 the script before any changes are made."""
1013 return self._DoCall("IncrementalOTA_VerifyBegin")
1014
Doug Zongker05d3dea2009-06-22 11:32:31 -07001015 def IncrementalOTA_VerifyEnd(self):
1016 """Called at the end of the verification phase of incremental OTA
1017 installation; additional checks can be placed here to abort the
1018 script before any changes are made."""
1019 return self._DoCall("IncrementalOTA_VerifyEnd")
1020
Doug Zongkere5ff5902012-01-17 10:55:37 -08001021 def IncrementalOTA_InstallBegin(self):
1022 """Called at the start of incremental OTA installation (after
1023 verification is complete)."""
1024 return self._DoCall("IncrementalOTA_InstallBegin")
1025
Doug Zongker05d3dea2009-06-22 11:32:31 -07001026 def IncrementalOTA_InstallEnd(self):
1027 """Called at the end of incremental OTA installation; typically
1028 this is used to install the image for the device's baseband
1029 processor."""
1030 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001031
1032class File(object):
1033 def __init__(self, name, data):
1034 self.name = name
1035 self.data = data
1036 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001037 self.sha1 = sha1(data).hexdigest()
1038
1039 @classmethod
1040 def FromLocalFile(cls, name, diskname):
1041 f = open(diskname, "rb")
1042 data = f.read()
1043 f.close()
1044 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001045
1046 def WriteToTemp(self):
1047 t = tempfile.NamedTemporaryFile()
1048 t.write(self.data)
1049 t.flush()
1050 return t
1051
Geremy Condra36bd3652014-02-06 19:45:10 -08001052 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001053 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001054
1055DIFF_PROGRAM_BY_EXT = {
1056 ".gz" : "imgdiff",
1057 ".zip" : ["imgdiff", "-z"],
1058 ".jar" : ["imgdiff", "-z"],
1059 ".apk" : ["imgdiff", "-z"],
1060 ".img" : "imgdiff",
1061 }
1062
1063class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001064 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001065 self.tf = tf
1066 self.sf = sf
1067 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001068 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001069
1070 def ComputePatch(self):
1071 """Compute the patch (as a string of data) needed to turn sf into
1072 tf. Returns the same tuple as GetPatch()."""
1073
1074 tf = self.tf
1075 sf = self.sf
1076
Doug Zongker24cd2802012-08-14 16:36:15 -07001077 if self.diff_program:
1078 diff_program = self.diff_program
1079 else:
1080 ext = os.path.splitext(tf.name)[1]
1081 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001082
1083 ttemp = tf.WriteToTemp()
1084 stemp = sf.WriteToTemp()
1085
1086 ext = os.path.splitext(tf.name)[1]
1087
1088 try:
1089 ptemp = tempfile.NamedTemporaryFile()
1090 if isinstance(diff_program, list):
1091 cmd = copy.copy(diff_program)
1092 else:
1093 cmd = [diff_program]
1094 cmd.append(stemp.name)
1095 cmd.append(ttemp.name)
1096 cmd.append(ptemp.name)
1097 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001098 err = []
1099 def run():
1100 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001101 if e:
1102 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001103 th = threading.Thread(target=run)
1104 th.start()
1105 th.join(timeout=300) # 5 mins
1106 if th.is_alive():
1107 print "WARNING: diff command timed out"
1108 p.terminate()
1109 th.join(5)
1110 if th.is_alive():
1111 p.kill()
1112 th.join()
1113
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001114 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001115 print "WARNING: failure running %s:\n%s\n" % (
1116 diff_program, "".join(err))
1117 self.patch = None
1118 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001119 diff = ptemp.read()
1120 finally:
1121 ptemp.close()
1122 stemp.close()
1123 ttemp.close()
1124
1125 self.patch = diff
1126 return self.tf, self.sf, self.patch
1127
1128
1129 def GetPatch(self):
1130 """Return a tuple (target_file, source_file, patch_data).
1131 patch_data may be None if ComputePatch hasn't been called, or if
1132 computing the patch failed."""
1133 return self.tf, self.sf, self.patch
1134
1135
1136def ComputeDifferences(diffs):
1137 """Call ComputePatch on all the Difference objects in 'diffs'."""
1138 print len(diffs), "diffs to compute"
1139
1140 # Do the largest files first, to try and reduce the long-pole effect.
1141 by_size = [(i.tf.size, i) for i in diffs]
1142 by_size.sort(reverse=True)
1143 by_size = [i[1] for i in by_size]
1144
1145 lock = threading.Lock()
1146 diff_iter = iter(by_size) # accessed under lock
1147
1148 def worker():
1149 try:
1150 lock.acquire()
1151 for d in diff_iter:
1152 lock.release()
1153 start = time.time()
1154 d.ComputePatch()
1155 dur = time.time() - start
1156 lock.acquire()
1157
1158 tf, sf, patch = d.GetPatch()
1159 if sf.name == tf.name:
1160 name = tf.name
1161 else:
1162 name = "%s (%s)" % (tf.name, sf.name)
1163 if patch is None:
1164 print "patching failed! %s" % (name,)
1165 else:
1166 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1167 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1168 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001169 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001170 print e
1171 raise
1172
1173 # start worker threads; wait for them all to finish.
1174 threads = [threading.Thread(target=worker)
1175 for i in range(OPTIONS.worker_threads)]
1176 for th in threads:
1177 th.start()
1178 while threads:
1179 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001180
1181
Dan Albert8b72aef2015-03-23 19:13:21 -07001182class BlockDifference(object):
1183 def __init__(self, partition, tgt, src=None, check_first_block=False,
1184 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001185 self.tgt = tgt
1186 self.src = src
1187 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001188 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001189
Tao Bao5ece99d2015-05-12 11:42:31 -07001190 # Due to http://b/20939131, check_first_block is disabled temporarily.
1191 assert not self.check_first_block
1192
Tao Baodd2a5892015-03-12 12:32:37 -07001193 if version is None:
1194 version = 1
1195 if OPTIONS.info_dict:
1196 version = max(
1197 int(i) for i in
1198 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1199 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001200
1201 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001202 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001203 tmpdir = tempfile.mkdtemp()
1204 OPTIONS.tempfiles.append(tmpdir)
1205 self.path = os.path.join(tmpdir, partition)
1206 b.Compute(self.path)
1207
Tao Baoe09359a2015-10-13 16:37:12 -07001208 if src is None:
1209 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1210 else:
1211 _, self.device = GetTypeAndDevice("/" + partition,
1212 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001213
1214 def WriteScript(self, script, output_zip, progress=None):
1215 if not self.src:
1216 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001217 script.Print("Patching %s image unconditionally..." % (self.partition,))
1218 else:
1219 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001220
Dan Albert8b72aef2015-03-23 19:13:21 -07001221 if progress:
1222 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001223 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001224 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001225
1226 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001227 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001228 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001229 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001230 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001231 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1232 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001233 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001234 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1235 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001236 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001237 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001238 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001239 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001240 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001241 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001242 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001243 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001244 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001245
Tao Baodd2a5892015-03-12 12:32:37 -07001246 # When generating incrementals for the system and vendor partitions,
1247 # explicitly check the first block (which contains the superblock) of
1248 # the partition to see if it's what we expect. If this check fails,
1249 # give an explicit log message about the partition having been
1250 # remounted R/W (the most likely explanation) and the need to flash to
1251 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001252 if self.check_first_block:
1253 self._CheckFirstBlock(script)
1254
Tao Baodd2a5892015-03-12 12:32:37 -07001255 # Abort the OTA update. Note that the incremental OTA cannot be applied
1256 # even if it may match the checksum of the target partition.
1257 # a) If version < 3, operations like move and erase will make changes
1258 # unconditionally and damage the partition.
1259 # b) If version >= 3, it won't even reach here.
1260 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1261 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001262
Tao Bao5fcaaef2015-06-01 13:40:49 -07001263 def _WritePostInstallVerifyScript(self, script):
1264 partition = self.partition
1265 script.Print('Verifying the updated %s image...' % (partition,))
1266 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1267 ranges = self.tgt.care_map
1268 ranges_str = ranges.to_string_raw()
1269 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1270 self.device, ranges_str,
1271 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001272
1273 # Bug: 20881595
1274 # Verify that extended blocks are really zeroed out.
1275 if self.tgt.extended:
1276 ranges_str = self.tgt.extended.to_string_raw()
1277 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1278 self.device, ranges_str,
1279 self._HashZeroBlocks(self.tgt.extended.size())))
1280 script.Print('Verified the updated %s image.' % (partition,))
1281 script.AppendExtra(
1282 'else\n'
1283 ' abort("%s partition has unexpected non-zero contents after OTA '
1284 'update");\n'
1285 'endif;' % (partition,))
1286 else:
1287 script.Print('Verified the updated %s image.' % (partition,))
1288
Tao Bao5fcaaef2015-06-01 13:40:49 -07001289 script.AppendExtra(
1290 'else\n'
1291 ' abort("%s partition has unexpected contents after OTA update");\n'
1292 'endif;' % (partition,))
1293
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001294 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001295 ZipWrite(output_zip,
1296 '{}.transfer.list'.format(self.path),
1297 '{}.transfer.list'.format(self.partition))
1298 ZipWrite(output_zip,
1299 '{}.new.dat'.format(self.path),
1300 '{}.new.dat'.format(self.partition))
1301 ZipWrite(output_zip,
1302 '{}.patch.dat'.format(self.path),
1303 '{}.patch.dat'.format(self.partition),
1304 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001305
Dan Albert8e0178d2015-01-27 15:53:15 -08001306 call = ('block_image_update("{device}", '
1307 'package_extract_file("{partition}.transfer.list"), '
1308 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1309 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001310 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001311
Dan Albert8b72aef2015-03-23 19:13:21 -07001312 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001313 data = source.ReadRangeSet(ranges)
1314 ctx = sha1()
1315
1316 for p in data:
1317 ctx.update(p)
1318
1319 return ctx.hexdigest()
1320
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001321 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1322 """Return the hash value for all zero blocks."""
1323 zero_block = '\x00' * 4096
1324 ctx = sha1()
1325 for _ in range(num_blocks):
1326 ctx.update(zero_block)
1327
1328 return ctx.hexdigest()
1329
Tao Bao5ece99d2015-05-12 11:42:31 -07001330 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1331 # remounting R/W. Will change the checking to a finer-grained way to
1332 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001333 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001334 r = rangelib.RangeSet((0, 1))
1335 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001336
1337 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1338 'abort("%s has been remounted R/W; '
1339 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001340 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001341 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001342
1343DataImage = blockimgdiff.DataImage
1344
1345
Doug Zongker96a57e72010-09-26 14:57:41 -07001346# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001347PARTITION_TYPES = {
1348 "yaffs2": "MTD",
1349 "mtd": "MTD",
1350 "ext4": "EMMC",
1351 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001352 "f2fs": "EMMC",
1353 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001354}
Doug Zongker96a57e72010-09-26 14:57:41 -07001355
1356def GetTypeAndDevice(mount_point, info):
1357 fstab = info["fstab"]
1358 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001359 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1360 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001361 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001362 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001363
1364
1365def ParseCertificate(data):
1366 """Parse a PEM-format certificate."""
1367 cert = []
1368 save = False
1369 for line in data.split("\n"):
1370 if "--END CERTIFICATE--" in line:
1371 break
1372 if save:
1373 cert.append(line)
1374 if "--BEGIN CERTIFICATE--" in line:
1375 save = True
1376 cert = "".join(cert).decode('base64')
1377 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001378
Doug Zongker412c02f2014-02-13 10:58:24 -08001379def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1380 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001381 """Generate a binary patch that creates the recovery image starting
1382 with the boot image. (Most of the space in these images is just the
1383 kernel, which is identical for the two, so the resulting patch
1384 should be efficient.) Add it to the output zip, along with a shell
1385 script that is run from init.rc on first boot to actually do the
1386 patching and install the new recovery image.
1387
1388 recovery_img and boot_img should be File objects for the
1389 corresponding images. info should be the dictionary returned by
1390 common.LoadInfoDict() on the input target_files.
1391 """
1392
Doug Zongker412c02f2014-02-13 10:58:24 -08001393 if info_dict is None:
1394 info_dict = OPTIONS.info_dict
1395
Doug Zongkerc9253822014-02-04 12:17:58 -08001396 diff_program = ["imgdiff"]
1397 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1398 if os.path.exists(path):
1399 diff_program.append("-b")
1400 diff_program.append(path)
1401 bonus_args = "-b /system/etc/recovery-resource.dat"
1402 else:
1403 bonus_args = ""
1404
1405 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1406 _, _, patch = d.ComputePatch()
1407 output_sink("recovery-from-boot.p", patch)
1408
Dan Albertebb19aa2015-03-27 19:11:53 -07001409 try:
Tao Baoe09359a2015-10-13 16:37:12 -07001410 # The following GetTypeAndDevice()s need to use the path in the target
1411 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001412 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 Bao610754e2015-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 Bao610754e2015-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 Bao610754e2015-07-07 18:31:47 -07001450 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001451 break
Tao Bao610754e2015-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)