blob: 0e8923b4aed144b988f314dda054a2613278bbf5 [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
Doug Zongkerc9253822014-02-04 12:17:58 -0800185 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
186 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
Doug Zongkerc9253822014-02-04 12:17:58 -0800208def LoadRecoveryFSTab(read_helper, fstab_version):
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
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700300 return d
301
302
Doug Zongker37974732010-09-16 17:44:38 -0700303def DumpInfoDict(d):
304 for k, v in sorted(d.items()):
305 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700306
Dan Albert8b72aef2015-03-23 19:13:21 -0700307
Doug Zongkerd5131602012-08-02 14:46:42 -0700308def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700309 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700310 'sourcedir'), and turn them into a boot image. Return the image
311 data, or None if sourcedir does not appear to contains files for
312 building the requested image."""
313
314 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
315 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
316 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700317
Doug Zongkerd5131602012-08-02 14:46:42 -0700318 if info_dict is None:
319 info_dict = OPTIONS.info_dict
320
Doug Zongkereef39442009-04-02 12:14:19 -0700321 ramdisk_img = tempfile.NamedTemporaryFile()
322 img = tempfile.NamedTemporaryFile()
323
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700324 if os.access(fs_config_file, os.F_OK):
325 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
326 else:
327 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
328 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700329 p2 = Run(["minigzip"],
330 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700331
332 p2.wait()
333 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700334 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
335 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700336
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800337 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
338 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
339
340 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700341
Benoit Fradina45a8682014-07-14 21:00:43 +0200342 fn = os.path.join(sourcedir, "second")
343 if os.access(fn, os.F_OK):
344 cmd.append("--second")
345 cmd.append(fn)
346
Doug Zongker171f1cd2009-06-15 22:36:37 -0700347 fn = os.path.join(sourcedir, "cmdline")
348 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700349 cmd.append("--cmdline")
350 cmd.append(open(fn).read().rstrip("\n"))
351
352 fn = os.path.join(sourcedir, "base")
353 if os.access(fn, os.F_OK):
354 cmd.append("--base")
355 cmd.append(open(fn).read().rstrip("\n"))
356
Ying Wang4de6b5b2010-08-25 14:29:34 -0700357 fn = os.path.join(sourcedir, "pagesize")
358 if os.access(fn, os.F_OK):
359 cmd.append("--pagesize")
360 cmd.append(open(fn).read().rstrip("\n"))
361
Doug Zongkerd5131602012-08-02 14:46:42 -0700362 args = info_dict.get("mkbootimg_args", None)
363 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700364 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700365
Tao Baod95e9fd2015-03-29 23:07:41 -0700366 img_unsigned = None
367 if info_dict.get("vboot", None):
368 img_unsigned = tempfile.NamedTemporaryFile()
369 cmd.extend(["--ramdisk", ramdisk_img.name,
370 "--output", img_unsigned.name])
371 else:
372 cmd.extend(["--ramdisk", ramdisk_img.name,
373 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700374
375 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700376 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700377 assert p.returncode == 0, "mkbootimg of %s image failed" % (
378 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700379
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100380 if (info_dict.get("boot_signer", None) == "true" and
381 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700382 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700383 cmd = [OPTIONS.boot_signer_path]
384 cmd.extend(OPTIONS.boot_signer_args)
385 cmd.extend([path, img.name,
386 info_dict["verity_key"] + ".pk8",
387 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700388 p = Run(cmd, stdout=subprocess.PIPE)
389 p.communicate()
390 assert p.returncode == 0, "boot_signer of %s image failed" % path
391
Tao Baod95e9fd2015-03-29 23:07:41 -0700392 # Sign the image if vboot is non-empty.
393 elif info_dict.get("vboot", None):
394 path = "/" + os.path.basename(sourcedir).lower()
395 img_keyblock = tempfile.NamedTemporaryFile()
396 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
397 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700398 info_dict["vboot_key"] + ".vbprivk",
399 info_dict["vboot_subkey"] + ".vbprivk",
400 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700401 img.name]
402 p = Run(cmd, stdout=subprocess.PIPE)
403 p.communicate()
404 assert p.returncode == 0, "vboot_signer of %s image failed" % path
405
Tao Baof3282b42015-04-01 11:21:55 -0700406 # Clean up the temp files.
407 img_unsigned.close()
408 img_keyblock.close()
409
Doug Zongkereef39442009-04-02 12:14:19 -0700410 img.seek(os.SEEK_SET, 0)
411 data = img.read()
412
413 ramdisk_img.close()
414 img.close()
415
416 return data
417
418
Doug Zongkerd5131602012-08-02 14:46:42 -0700419def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
420 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800421 """Return a File object (with name 'name') with the desired bootable
422 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700423 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
424 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800425 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700426
Doug Zongker55d93282011-01-25 17:03:34 -0800427 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
428 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700429 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800430 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700431
432 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
433 if os.path.exists(prebuilt_path):
434 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
435 return File.FromLocalFile(name, prebuilt_path)
436
437 print "building image from target_files %s..." % (tree_subdir,)
438 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
439 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
440 os.path.join(unpack_dir, fs_config),
441 info_dict)
442 if data:
443 return File(name, data)
444 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800445
Doug Zongkereef39442009-04-02 12:14:19 -0700446
Doug Zongker75f17362009-12-08 13:46:44 -0800447def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800448 """Unzip the given archive into a temporary directory and return the name.
449
450 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
451 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
452
453 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
454 main file), open for reading.
455 """
Doug Zongkereef39442009-04-02 12:14:19 -0700456
457 tmp = tempfile.mkdtemp(prefix="targetfiles-")
458 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800459
460 def unzip_to_dir(filename, dirname):
461 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
462 if pattern is not None:
463 cmd.append(pattern)
464 p = Run(cmd, stdout=subprocess.PIPE)
465 p.communicate()
466 if p.returncode != 0:
467 raise ExternalError("failed to unzip input target-files \"%s\"" %
468 (filename,))
469
470 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
471 if m:
472 unzip_to_dir(m.group(1), tmp)
473 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
474 filename = m.group(1)
475 else:
476 unzip_to_dir(filename, tmp)
477
478 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700479
480
481def GetKeyPasswords(keylist):
482 """Given a list of keys, prompt the user to enter passwords for
483 those which require them. Return a {key: password} dict. password
484 will be None if the key has no password."""
485
Doug Zongker8ce7c252009-05-22 13:34:54 -0700486 no_passwords = []
487 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700488 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700489 devnull = open("/dev/null", "w+b")
490 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800491 # We don't need a password for things that aren't really keys.
492 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700493 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700494 continue
495
T.R. Fullhart37e10522013-03-18 10:31:26 -0700496 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700497 "-inform", "DER", "-nocrypt"],
498 stdin=devnull.fileno(),
499 stdout=devnull.fileno(),
500 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700501 p.communicate()
502 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700503 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700504 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700505 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700506 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
507 "-inform", "DER", "-passin", "pass:"],
508 stdin=devnull.fileno(),
509 stdout=devnull.fileno(),
510 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700511 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700512 if p.returncode == 0:
513 # Encrypted key with empty string as password.
514 key_passwords[k] = ''
515 elif stderr.startswith('Error decrypting key'):
516 # Definitely encrypted key.
517 # It would have said "Error reading key" if it didn't parse correctly.
518 need_passwords.append(k)
519 else:
520 # Potentially, a type of key that openssl doesn't understand.
521 # We'll let the routines in signapk.jar handle it.
522 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700523 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700524
T.R. Fullhart37e10522013-03-18 10:31:26 -0700525 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700526 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700527 return key_passwords
528
529
Doug Zongker951495f2009-08-14 12:44:19 -0700530def SignFile(input_name, output_name, key, password, align=None,
531 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700532 """Sign the input_name zip/jar/apk, producing output_name. Use the
533 given key and password (the latter may be None if the key does not
534 have a password.
535
536 If align is an integer > 1, zipalign is run to align stored files in
537 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700538
539 If whole_file is true, use the "-w" option to SignApk to embed a
540 signature that covers the whole file in the archive comment of the
541 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700542 """
Doug Zongker951495f2009-08-14 12:44:19 -0700543
Doug Zongkereef39442009-04-02 12:14:19 -0700544 if align == 0 or align == 1:
545 align = None
546
547 if align:
548 temp = tempfile.NamedTemporaryFile()
549 sign_name = temp.name
550 else:
551 sign_name = output_name
552
Baligh Uddin339ee492014-09-05 11:18:07 -0700553 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700554 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
555 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700556 if whole_file:
557 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700558 cmd.extend([key + OPTIONS.public_key_suffix,
559 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700560 input_name, sign_name])
561
562 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700563 if password is not None:
564 password += "\n"
565 p.communicate(password)
566 if p.returncode != 0:
567 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
568
569 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700570 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700571 p.communicate()
572 if p.returncode != 0:
573 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
574 temp.close()
575
576
Doug Zongker37974732010-09-16 17:44:38 -0700577def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700578 """Check the data string passed against the max size limit, if
579 any, for the given target. Raise exception if the data is too big.
580 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700581
Dan Albert8b72aef2015-03-23 19:13:21 -0700582 if target.endswith(".img"):
583 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700584 mount_point = "/" + target
585
Ying Wangf8824af2014-06-03 14:07:27 -0700586 fs_type = None
587 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700588 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700589 if mount_point == "/userdata":
590 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700591 p = info_dict["fstab"][mount_point]
592 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800593 device = p.device
594 if "/" in device:
595 device = device[device.rfind("/")+1:]
596 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700597 if not fs_type or not limit:
598 return
Doug Zongkereef39442009-04-02 12:14:19 -0700599
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700600 if fs_type == "yaffs2":
601 # image size should be increased by 1/64th to account for the
602 # spare area (64 bytes per 2k page)
603 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800604 size = len(data)
605 pct = float(size) * 100.0 / limit
606 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
607 if pct >= 99.0:
608 raise ExternalError(msg)
609 elif pct >= 95.0:
610 print
611 print " WARNING: ", msg
612 print
613 elif OPTIONS.verbose:
614 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700615
616
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800617def ReadApkCerts(tf_zip):
618 """Given a target_files ZipFile, parse the META/apkcerts.txt file
619 and return a {package: cert} dict."""
620 certmap = {}
621 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
622 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700623 if not line:
624 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800625 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
626 r'private_key="(.*)"$', line)
627 if m:
628 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700629 public_key_suffix_len = len(OPTIONS.public_key_suffix)
630 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800631 if cert in SPECIAL_CERT_STRINGS and not privkey:
632 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700633 elif (cert.endswith(OPTIONS.public_key_suffix) and
634 privkey.endswith(OPTIONS.private_key_suffix) and
635 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
636 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800637 else:
638 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
639 return certmap
640
641
Doug Zongkereef39442009-04-02 12:14:19 -0700642COMMON_DOCSTRING = """
643 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700644 Prepend <dir>/bin to the list of places to search for binaries
645 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700646
Doug Zongker05d3dea2009-06-22 11:32:31 -0700647 -s (--device_specific) <file>
648 Path to the python module containing device-specific
649 releasetools code.
650
Doug Zongker8bec09e2009-11-30 15:37:14 -0800651 -x (--extra) <key=value>
652 Add a key/value pair to the 'extras' dict, which device-specific
653 extension code may look at.
654
Doug Zongkereef39442009-04-02 12:14:19 -0700655 -v (--verbose)
656 Show command lines being executed.
657
658 -h (--help)
659 Display this usage message and exit.
660"""
661
662def Usage(docstring):
663 print docstring.rstrip("\n")
664 print COMMON_DOCSTRING
665
666
667def ParseOptions(argv,
668 docstring,
669 extra_opts="", extra_long_opts=(),
670 extra_option_handler=None):
671 """Parse the options in argv and return any arguments that aren't
672 flags. docstring is the calling module's docstring, to be displayed
673 for errors and -h. extra_opts and extra_long_opts are for flags
674 defined by the caller, which are processed by passing them to
675 extra_option_handler."""
676
677 try:
678 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800679 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700680 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700681 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700682 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
683 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800684 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700685 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700686 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700687 Usage(docstring)
688 print "**", str(err), "**"
689 sys.exit(2)
690
Doug Zongkereef39442009-04-02 12:14:19 -0700691 for o, a in opts:
692 if o in ("-h", "--help"):
693 Usage(docstring)
694 sys.exit()
695 elif o in ("-v", "--verbose"):
696 OPTIONS.verbose = True
697 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700698 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700699 elif o in ("--signapk_path",):
700 OPTIONS.signapk_path = a
701 elif o in ("--extra_signapk_args",):
702 OPTIONS.extra_signapk_args = shlex.split(a)
703 elif o in ("--java_path",):
704 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700705 elif o in ("--java_args",):
706 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700707 elif o in ("--public_key_suffix",):
708 OPTIONS.public_key_suffix = a
709 elif o in ("--private_key_suffix",):
710 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800711 elif o in ("--boot_signer_path",):
712 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700713 elif o in ("--boot_signer_args",):
714 OPTIONS.boot_signer_args = shlex.split(a)
715 elif o in ("--verity_signer_path",):
716 OPTIONS.verity_signer_path = a
717 elif o in ("--verity_signer_args",):
718 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700719 elif o in ("-s", "--device_specific"):
720 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800721 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800722 key, value = a.split("=", 1)
723 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700724 else:
725 if extra_option_handler is None or not extra_option_handler(o, a):
726 assert False, "unknown option \"%s\"" % (o,)
727
Doug Zongker85448772014-09-09 14:59:20 -0700728 if OPTIONS.search_path:
729 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
730 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700731
732 return args
733
734
Doug Zongkerfc44a512014-08-26 13:10:25 -0700735def MakeTempFile(prefix=None, suffix=None):
736 """Make a temp file and add it to the list of things to be deleted
737 when Cleanup() is called. Return the filename."""
738 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
739 os.close(fd)
740 OPTIONS.tempfiles.append(fn)
741 return fn
742
743
Doug Zongkereef39442009-04-02 12:14:19 -0700744def Cleanup():
745 for i in OPTIONS.tempfiles:
746 if os.path.isdir(i):
747 shutil.rmtree(i)
748 else:
749 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700750
751
752class PasswordManager(object):
753 def __init__(self):
754 self.editor = os.getenv("EDITOR", None)
755 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
756
757 def GetPasswords(self, items):
758 """Get passwords corresponding to each string in 'items',
759 returning a dict. (The dict may have keys in addition to the
760 values in 'items'.)
761
762 Uses the passwords in $ANDROID_PW_FILE if available, letting the
763 user edit that file to add more needed passwords. If no editor is
764 available, or $ANDROID_PW_FILE isn't define, prompts the user
765 interactively in the ordinary way.
766 """
767
768 current = self.ReadFile()
769
770 first = True
771 while True:
772 missing = []
773 for i in items:
774 if i not in current or not current[i]:
775 missing.append(i)
776 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700777 if not missing:
778 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700779
780 for i in missing:
781 current[i] = ""
782
783 if not first:
784 print "key file %s still missing some passwords." % (self.pwfile,)
785 answer = raw_input("try to edit again? [y]> ").strip()
786 if answer and answer[0] not in 'yY':
787 raise RuntimeError("key passwords unavailable")
788 first = False
789
790 current = self.UpdateAndReadFile(current)
791
Dan Albert8b72aef2015-03-23 19:13:21 -0700792 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700793 """Prompt the user to enter a value (password) for each key in
794 'current' whose value is fales. Returns a new dict with all the
795 values.
796 """
797 result = {}
798 for k, v in sorted(current.iteritems()):
799 if v:
800 result[k] = v
801 else:
802 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700803 result[k] = getpass.getpass(
804 "Enter password for %s key> " % k).strip()
805 if result[k]:
806 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700807 return result
808
809 def UpdateAndReadFile(self, current):
810 if not self.editor or not self.pwfile:
811 return self.PromptResult(current)
812
813 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700814 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700815 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
816 f.write("# (Additional spaces are harmless.)\n\n")
817
818 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700819 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
820 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700821 f.write("[[[ %s ]]] %s\n" % (v, k))
822 if not v and first_line is None:
823 # position cursor on first line with no password.
824 first_line = i + 4
825 f.close()
826
827 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
828 _, _ = p.communicate()
829
830 return self.ReadFile()
831
832 def ReadFile(self):
833 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700834 if self.pwfile is None:
835 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700836 try:
837 f = open(self.pwfile, "r")
838 for line in f:
839 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700840 if not line or line[0] == '#':
841 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700842 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
843 if not m:
844 print "failed to parse password file: ", line
845 else:
846 result[m.group(2)] = m.group(1)
847 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700848 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700849 if e.errno != errno.ENOENT:
850 print "error reading password file: ", str(e)
851 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700852
853
Dan Albert8e0178d2015-01-27 15:53:15 -0800854def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
855 compress_type=None):
856 import datetime
857
858 # http://b/18015246
859 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
860 # for files larger than 2GiB. We can work around this by adjusting their
861 # limit. Note that `zipfile.writestr()` will not work for strings larger than
862 # 2GiB. The Python interpreter sometimes rejects strings that large (though
863 # it isn't clear to me exactly what circumstances cause this).
864 # `zipfile.write()` must be used directly to work around this.
865 #
866 # This mess can be avoided if we port to python3.
867 saved_zip64_limit = zipfile.ZIP64_LIMIT
868 zipfile.ZIP64_LIMIT = (1 << 32) - 1
869
870 if compress_type is None:
871 compress_type = zip_file.compression
872 if arcname is None:
873 arcname = filename
874
875 saved_stat = os.stat(filename)
876
877 try:
878 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
879 # file to be zipped and reset it when we're done.
880 os.chmod(filename, perms)
881
882 # Use a fixed timestamp so the output is repeatable.
883 epoch = datetime.datetime.fromtimestamp(0)
884 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
885 os.utime(filename, (timestamp, timestamp))
886
887 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
888 finally:
889 os.chmod(filename, saved_stat.st_mode)
890 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
891 zipfile.ZIP64_LIMIT = saved_zip64_limit
892
893
Tao Bao58c1b962015-05-20 09:32:18 -0700894def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700895 compress_type=None):
896 """Wrap zipfile.writestr() function to work around the zip64 limit.
897
898 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
899 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
900 when calling crc32(bytes).
901
902 But it still works fine to write a shorter string into a large zip file.
903 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
904 when we know the string won't be too long.
905 """
906
907 saved_zip64_limit = zipfile.ZIP64_LIMIT
908 zipfile.ZIP64_LIMIT = (1 << 32) - 1
909
910 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
911 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700912 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700913 if perms is None:
914 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800915 else:
Tao Baof3282b42015-04-01 11:21:55 -0700916 zinfo = zinfo_or_arcname
917
918 # If compress_type is given, it overrides the value in zinfo.
919 if compress_type is not None:
920 zinfo.compress_type = compress_type
921
Tao Bao58c1b962015-05-20 09:32:18 -0700922 # If perms is given, it has a priority.
923 if perms is not None:
924 zinfo.external_attr = perms << 16
925
Tao Baof3282b42015-04-01 11:21:55 -0700926 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700927 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
928
Dan Albert8b72aef2015-03-23 19:13:21 -0700929 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700930 zipfile.ZIP64_LIMIT = saved_zip64_limit
931
932
933def ZipClose(zip_file):
934 # http://b/18015246
935 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
936 # central directory.
937 saved_zip64_limit = zipfile.ZIP64_LIMIT
938 zipfile.ZIP64_LIMIT = (1 << 32) - 1
939
940 zip_file.close()
941
942 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700943
944
945class DeviceSpecificParams(object):
946 module = None
947 def __init__(self, **kwargs):
948 """Keyword arguments to the constructor become attributes of this
949 object, which is passed to all functions in the device-specific
950 module."""
951 for k, v in kwargs.iteritems():
952 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800953 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700954
955 if self.module is None:
956 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700957 if not path:
958 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700959 try:
960 if os.path.isdir(path):
961 info = imp.find_module("releasetools", [path])
962 else:
963 d, f = os.path.split(path)
964 b, x = os.path.splitext(f)
965 if x == ".py":
966 f = b
967 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800968 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700969 self.module = imp.load_module("device_specific", *info)
970 except ImportError:
971 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700972
973 def _DoCall(self, function_name, *args, **kwargs):
974 """Call the named function in the device-specific module, passing
975 the given args and kwargs. The first argument to the call will be
976 the DeviceSpecific object itself. If there is no module, or the
977 module does not define the function, return the value of the
978 'default' kwarg (which itself defaults to None)."""
979 if self.module is None or not hasattr(self.module, function_name):
980 return kwargs.get("default", None)
981 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
982
983 def FullOTA_Assertions(self):
984 """Called after emitting the block of assertions at the top of a
985 full OTA package. Implementations can add whatever additional
986 assertions they like."""
987 return self._DoCall("FullOTA_Assertions")
988
Doug Zongkere5ff5902012-01-17 10:55:37 -0800989 def FullOTA_InstallBegin(self):
990 """Called at the start of full OTA installation."""
991 return self._DoCall("FullOTA_InstallBegin")
992
Doug Zongker05d3dea2009-06-22 11:32:31 -0700993 def FullOTA_InstallEnd(self):
994 """Called at the end of full OTA installation; typically this is
995 used to install the image for the device's baseband processor."""
996 return self._DoCall("FullOTA_InstallEnd")
997
998 def IncrementalOTA_Assertions(self):
999 """Called after emitting the block of assertions at the top of an
1000 incremental OTA package. Implementations can add whatever
1001 additional assertions they like."""
1002 return self._DoCall("IncrementalOTA_Assertions")
1003
Doug Zongkere5ff5902012-01-17 10:55:37 -08001004 def IncrementalOTA_VerifyBegin(self):
1005 """Called at the start of the verification phase of incremental
1006 OTA installation; additional checks can be placed here to abort
1007 the script before any changes are made."""
1008 return self._DoCall("IncrementalOTA_VerifyBegin")
1009
Doug Zongker05d3dea2009-06-22 11:32:31 -07001010 def IncrementalOTA_VerifyEnd(self):
1011 """Called at the end of the verification phase of incremental OTA
1012 installation; additional checks can be placed here to abort the
1013 script before any changes are made."""
1014 return self._DoCall("IncrementalOTA_VerifyEnd")
1015
Doug Zongkere5ff5902012-01-17 10:55:37 -08001016 def IncrementalOTA_InstallBegin(self):
1017 """Called at the start of incremental OTA installation (after
1018 verification is complete)."""
1019 return self._DoCall("IncrementalOTA_InstallBegin")
1020
Doug Zongker05d3dea2009-06-22 11:32:31 -07001021 def IncrementalOTA_InstallEnd(self):
1022 """Called at the end of incremental OTA installation; typically
1023 this is used to install the image for the device's baseband
1024 processor."""
1025 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001026
1027class File(object):
1028 def __init__(self, name, data):
1029 self.name = name
1030 self.data = data
1031 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001032 self.sha1 = sha1(data).hexdigest()
1033
1034 @classmethod
1035 def FromLocalFile(cls, name, diskname):
1036 f = open(diskname, "rb")
1037 data = f.read()
1038 f.close()
1039 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001040
1041 def WriteToTemp(self):
1042 t = tempfile.NamedTemporaryFile()
1043 t.write(self.data)
1044 t.flush()
1045 return t
1046
Geremy Condra36bd3652014-02-06 19:45:10 -08001047 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001048 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001049
1050DIFF_PROGRAM_BY_EXT = {
1051 ".gz" : "imgdiff",
1052 ".zip" : ["imgdiff", "-z"],
1053 ".jar" : ["imgdiff", "-z"],
1054 ".apk" : ["imgdiff", "-z"],
1055 ".img" : "imgdiff",
1056 }
1057
1058class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001059 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001060 self.tf = tf
1061 self.sf = sf
1062 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001063 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001064
1065 def ComputePatch(self):
1066 """Compute the patch (as a string of data) needed to turn sf into
1067 tf. Returns the same tuple as GetPatch()."""
1068
1069 tf = self.tf
1070 sf = self.sf
1071
Doug Zongker24cd2802012-08-14 16:36:15 -07001072 if self.diff_program:
1073 diff_program = self.diff_program
1074 else:
1075 ext = os.path.splitext(tf.name)[1]
1076 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001077
1078 ttemp = tf.WriteToTemp()
1079 stemp = sf.WriteToTemp()
1080
1081 ext = os.path.splitext(tf.name)[1]
1082
1083 try:
1084 ptemp = tempfile.NamedTemporaryFile()
1085 if isinstance(diff_program, list):
1086 cmd = copy.copy(diff_program)
1087 else:
1088 cmd = [diff_program]
1089 cmd.append(stemp.name)
1090 cmd.append(ttemp.name)
1091 cmd.append(ptemp.name)
1092 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001093 err = []
1094 def run():
1095 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001096 if e:
1097 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001098 th = threading.Thread(target=run)
1099 th.start()
1100 th.join(timeout=300) # 5 mins
1101 if th.is_alive():
1102 print "WARNING: diff command timed out"
1103 p.terminate()
1104 th.join(5)
1105 if th.is_alive():
1106 p.kill()
1107 th.join()
1108
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001109 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001110 print "WARNING: failure running %s:\n%s\n" % (
1111 diff_program, "".join(err))
1112 self.patch = None
1113 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001114 diff = ptemp.read()
1115 finally:
1116 ptemp.close()
1117 stemp.close()
1118 ttemp.close()
1119
1120 self.patch = diff
1121 return self.tf, self.sf, self.patch
1122
1123
1124 def GetPatch(self):
1125 """Return a tuple (target_file, source_file, patch_data).
1126 patch_data may be None if ComputePatch hasn't been called, or if
1127 computing the patch failed."""
1128 return self.tf, self.sf, self.patch
1129
1130
1131def ComputeDifferences(diffs):
1132 """Call ComputePatch on all the Difference objects in 'diffs'."""
1133 print len(diffs), "diffs to compute"
1134
1135 # Do the largest files first, to try and reduce the long-pole effect.
1136 by_size = [(i.tf.size, i) for i in diffs]
1137 by_size.sort(reverse=True)
1138 by_size = [i[1] for i in by_size]
1139
1140 lock = threading.Lock()
1141 diff_iter = iter(by_size) # accessed under lock
1142
1143 def worker():
1144 try:
1145 lock.acquire()
1146 for d in diff_iter:
1147 lock.release()
1148 start = time.time()
1149 d.ComputePatch()
1150 dur = time.time() - start
1151 lock.acquire()
1152
1153 tf, sf, patch = d.GetPatch()
1154 if sf.name == tf.name:
1155 name = tf.name
1156 else:
1157 name = "%s (%s)" % (tf.name, sf.name)
1158 if patch is None:
1159 print "patching failed! %s" % (name,)
1160 else:
1161 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1162 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1163 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001164 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001165 print e
1166 raise
1167
1168 # start worker threads; wait for them all to finish.
1169 threads = [threading.Thread(target=worker)
1170 for i in range(OPTIONS.worker_threads)]
1171 for th in threads:
1172 th.start()
1173 while threads:
1174 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001175
1176
Dan Albert8b72aef2015-03-23 19:13:21 -07001177class BlockDifference(object):
1178 def __init__(self, partition, tgt, src=None, check_first_block=False,
1179 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001180 self.tgt = tgt
1181 self.src = src
1182 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001183 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001184
Tao Bao5ece99d2015-05-12 11:42:31 -07001185 # Due to http://b/20939131, check_first_block is disabled temporarily.
1186 assert not self.check_first_block
1187
Tao Baodd2a5892015-03-12 12:32:37 -07001188 if version is None:
1189 version = 1
1190 if OPTIONS.info_dict:
1191 version = max(
1192 int(i) for i in
1193 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1194 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001195
1196 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001197 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001198 tmpdir = tempfile.mkdtemp()
1199 OPTIONS.tempfiles.append(tmpdir)
1200 self.path = os.path.join(tmpdir, partition)
1201 b.Compute(self.path)
1202
Tao Baoe09359a2015-10-13 16:37:12 -07001203 if src is None:
1204 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1205 else:
1206 _, self.device = GetTypeAndDevice("/" + partition,
1207 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001208
1209 def WriteScript(self, script, output_zip, progress=None):
1210 if not self.src:
1211 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001212 script.Print("Patching %s image unconditionally..." % (self.partition,))
1213 else:
1214 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001215
Dan Albert8b72aef2015-03-23 19:13:21 -07001216 if progress:
1217 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001218 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001219 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001220
1221 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001222 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001223 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001224 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001225 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001226 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1227 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001228 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001229 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1230 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001231 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001232 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001233 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001234 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001235 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001236 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001237 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001238 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001239 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001240
Tao Baodd2a5892015-03-12 12:32:37 -07001241 # When generating incrementals for the system and vendor partitions,
1242 # explicitly check the first block (which contains the superblock) of
1243 # the partition to see if it's what we expect. If this check fails,
1244 # give an explicit log message about the partition having been
1245 # remounted R/W (the most likely explanation) and the need to flash to
1246 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001247 if self.check_first_block:
1248 self._CheckFirstBlock(script)
1249
Tao Baodd2a5892015-03-12 12:32:37 -07001250 # Abort the OTA update. Note that the incremental OTA cannot be applied
1251 # even if it may match the checksum of the target partition.
1252 # a) If version < 3, operations like move and erase will make changes
1253 # unconditionally and damage the partition.
1254 # b) If version >= 3, it won't even reach here.
1255 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1256 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001257
Tao Bao5fcaaef2015-06-01 13:40:49 -07001258 def _WritePostInstallVerifyScript(self, script):
1259 partition = self.partition
1260 script.Print('Verifying the updated %s image...' % (partition,))
1261 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1262 ranges = self.tgt.care_map
1263 ranges_str = ranges.to_string_raw()
1264 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1265 self.device, ranges_str,
1266 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001267
1268 # Bug: 20881595
1269 # Verify that extended blocks are really zeroed out.
1270 if self.tgt.extended:
1271 ranges_str = self.tgt.extended.to_string_raw()
1272 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1273 self.device, ranges_str,
1274 self._HashZeroBlocks(self.tgt.extended.size())))
1275 script.Print('Verified the updated %s image.' % (partition,))
1276 script.AppendExtra(
1277 'else\n'
1278 ' abort("%s partition has unexpected non-zero contents after OTA '
1279 'update");\n'
1280 'endif;' % (partition,))
1281 else:
1282 script.Print('Verified the updated %s image.' % (partition,))
1283
Tao Bao5fcaaef2015-06-01 13:40:49 -07001284 script.AppendExtra(
1285 'else\n'
1286 ' abort("%s partition has unexpected contents after OTA update");\n'
1287 'endif;' % (partition,))
1288
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001289 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001290 ZipWrite(output_zip,
1291 '{}.transfer.list'.format(self.path),
1292 '{}.transfer.list'.format(self.partition))
1293 ZipWrite(output_zip,
1294 '{}.new.dat'.format(self.path),
1295 '{}.new.dat'.format(self.partition))
1296 ZipWrite(output_zip,
1297 '{}.patch.dat'.format(self.path),
1298 '{}.patch.dat'.format(self.partition),
1299 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001300
Dan Albert8e0178d2015-01-27 15:53:15 -08001301 call = ('block_image_update("{device}", '
1302 'package_extract_file("{partition}.transfer.list"), '
1303 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1304 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001305 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001306
Dan Albert8b72aef2015-03-23 19:13:21 -07001307 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001308 data = source.ReadRangeSet(ranges)
1309 ctx = sha1()
1310
1311 for p in data:
1312 ctx.update(p)
1313
1314 return ctx.hexdigest()
1315
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001316 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1317 """Return the hash value for all zero blocks."""
1318 zero_block = '\x00' * 4096
1319 ctx = sha1()
1320 for _ in range(num_blocks):
1321 ctx.update(zero_block)
1322
1323 return ctx.hexdigest()
1324
Tao Bao5ece99d2015-05-12 11:42:31 -07001325 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1326 # remounting R/W. Will change the checking to a finer-grained way to
1327 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001328 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001329 r = rangelib.RangeSet((0, 1))
1330 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001331
1332 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1333 'abort("%s has been remounted R/W; '
1334 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001335 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001336 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001337
1338DataImage = blockimgdiff.DataImage
1339
1340
Doug Zongker96a57e72010-09-26 14:57:41 -07001341# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001342PARTITION_TYPES = {
1343 "yaffs2": "MTD",
1344 "mtd": "MTD",
1345 "ext4": "EMMC",
1346 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001347 "f2fs": "EMMC",
1348 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001349}
Doug Zongker96a57e72010-09-26 14:57:41 -07001350
1351def GetTypeAndDevice(mount_point, info):
1352 fstab = info["fstab"]
1353 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001354 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1355 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001356 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001357 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001358
1359
1360def ParseCertificate(data):
1361 """Parse a PEM-format certificate."""
1362 cert = []
1363 save = False
1364 for line in data.split("\n"):
1365 if "--END CERTIFICATE--" in line:
1366 break
1367 if save:
1368 cert.append(line)
1369 if "--BEGIN CERTIFICATE--" in line:
1370 save = True
1371 cert = "".join(cert).decode('base64')
1372 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001373
Doug Zongker412c02f2014-02-13 10:58:24 -08001374def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1375 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001376 """Generate a binary patch that creates the recovery image starting
1377 with the boot image. (Most of the space in these images is just the
1378 kernel, which is identical for the two, so the resulting patch
1379 should be efficient.) Add it to the output zip, along with a shell
1380 script that is run from init.rc on first boot to actually do the
1381 patching and install the new recovery image.
1382
1383 recovery_img and boot_img should be File objects for the
1384 corresponding images. info should be the dictionary returned by
1385 common.LoadInfoDict() on the input target_files.
1386 """
1387
Doug Zongker412c02f2014-02-13 10:58:24 -08001388 if info_dict is None:
1389 info_dict = OPTIONS.info_dict
1390
Doug Zongkerc9253822014-02-04 12:17:58 -08001391 diff_program = ["imgdiff"]
1392 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1393 if os.path.exists(path):
1394 diff_program.append("-b")
1395 diff_program.append(path)
1396 bonus_args = "-b /system/etc/recovery-resource.dat"
1397 else:
1398 bonus_args = ""
1399
1400 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1401 _, _, patch = d.ComputePatch()
1402 output_sink("recovery-from-boot.p", patch)
1403
Dan Albertebb19aa2015-03-27 19:11:53 -07001404 try:
Tao Baoe09359a2015-10-13 16:37:12 -07001405 # The following GetTypeAndDevice()s need to use the path in the target
1406 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001407 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1408 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1409 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001410 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001411
1412 sh = """#!/system/bin/sh
1413if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1414 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"
1415else
1416 log -t recovery "Recovery image already installed"
1417fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001418""" % {'boot_size': boot_img.size,
1419 'boot_sha1': boot_img.sha1,
1420 'recovery_size': recovery_img.size,
1421 'recovery_sha1': recovery_img.sha1,
1422 'boot_type': boot_type,
1423 'boot_device': boot_device,
1424 'recovery_type': recovery_type,
1425 'recovery_device': recovery_device,
1426 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001427
1428 # The install script location moved from /system/etc to /system/bin
Tao Bao610754e2015-07-07 18:31:47 -07001429 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001430 # target-files expects it to be, and put it there.
1431 sh_location = "etc/install-recovery.sh"
Tao Bao610754e2015-07-07 18:31:47 -07001432 found = False
1433 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
1434 init_rc_files = os.listdir(init_rc_dir)
1435 for init_rc_file in init_rc_files:
1436 if (not init_rc_file.startswith('init.') or
1437 not init_rc_file.endswith('.rc')):
1438 continue
1439
1440 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001441 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001442 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001443 if m:
1444 sh_location = m.group(1)
Tao Bao610754e2015-07-07 18:31:47 -07001445 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001446 break
Tao Bao610754e2015-07-07 18:31:47 -07001447
1448 if found:
1449 break
1450
1451 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001452
1453 output_sink(sh_location, sh)