blob: 701bb7c378ddcfd20becfd77a1acdcd8edd02456 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
Dan Albert8b72aef2015-03-23 19:13:21 -070033import rangelib
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Tao Bao2ed665a2015-04-01 11:21:55 -070035from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036
Doug Zongkereef39442009-04-02 12:14:19 -070037
Dan Albert8b72aef2015-03-23 19:13:21 -070038class Options(object):
39 def __init__(self):
40 platform_search_path = {
41 "linux2": "out/host/linux-x86",
42 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070043 }
Doug Zongker85448772014-09-09 14:59:20 -070044
Dan Albert8b72aef2015-03-23 19:13:21 -070045 self.search_path = platform_search_path.get(sys.platform, None)
46 self.signapk_path = "framework/signapk.jar" # Relative to search_path
47 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
49 self.java_args = "-Xmx2048m" # JVM Args
50 self.public_key_suffix = ".x509.pem"
51 self.private_key_suffix = ".pk8"
Baligh Uddin852a5b52014-11-20 09:52:05 -080052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Dan Albert8b72aef2015-03-23 19:13:21 -070054 self.verbose = False
55 self.tempfiles = []
56 self.device_specific = None
57 self.extras = {}
58 self.info_dict = None
59 self.worker_threads = None
60
61
62OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070063
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080064
65# Values for "certificate" in apkcerts that mean special things.
66SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
67
68
Dan Albert8b72aef2015-03-23 19:13:21 -070069class ExternalError(RuntimeError):
70 pass
Doug Zongkereef39442009-04-02 12:14:19 -070071
72
73def Run(args, **kwargs):
74 """Create and return a subprocess.Popen object, printing the command
75 line on the terminal if -v was specified."""
76 if OPTIONS.verbose:
77 print " running: ", " ".join(args)
78 return subprocess.Popen(args, **kwargs)
79
80
Ying Wang7e6d4e42010-12-13 16:25:36 -080081def CloseInheritedPipes():
82 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
83 before doing other work."""
84 if platform.system() != "Darwin":
85 return
86 for d in range(3, 1025):
87 try:
88 stat = os.fstat(d)
89 if stat is not None:
90 pipebit = stat[0] & 0x1000
91 if pipebit != 0:
92 os.close(d)
93 except OSError:
94 pass
95
96
Dan Albert8b72aef2015-03-23 19:13:21 -070097def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070098 """Read and parse the META/misc_info.txt key/value pairs from the
99 input target files and return a dict."""
100
Doug Zongkerc9253822014-02-04 12:17:58 -0800101 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700102 if isinstance(input_file, zipfile.ZipFile):
103 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800104 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700105 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800106 try:
107 with open(path) as f:
108 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700109 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800110 if e.errno == errno.ENOENT:
111 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700112 d = {}
113 try:
Michael Runge6e836112014-04-15 17:40:21 -0700114 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700115 except KeyError:
116 # ok if misc_info.txt doesn't exist
117 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700118
Doug Zongker37974732010-09-16 17:44:38 -0700119 # backwards compatibility: These values used to be in their own
120 # files. Look for them, in case we're processing an old
121 # target_files zip.
122
123 if "mkyaffs2_extra_flags" not in d:
124 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700125 d["mkyaffs2_extra_flags"] = read_helper(
126 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700127 except KeyError:
128 # ok if flags don't exist
129 pass
130
131 if "recovery_api_version" not in d:
132 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700133 d["recovery_api_version"] = read_helper(
134 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700135 except KeyError:
136 raise ValueError("can't find recovery API version in input target-files")
137
138 if "tool_extensions" not in d:
139 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800140 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700141 except KeyError:
142 # ok if extensions don't exist
143 pass
144
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800145 if "fstab_version" not in d:
146 d["fstab_version"] = "1"
147
Doug Zongker37974732010-09-16 17:44:38 -0700148 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800149 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700150 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700151 if not line:
152 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700153 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700154 if not value:
155 continue
Doug Zongker37974732010-09-16 17:44:38 -0700156 if name == "blocksize":
157 d[name] = value
158 else:
159 d[name + "_size"] = value
160 except KeyError:
161 pass
162
163 def makeint(key):
164 if key in d:
165 d[key] = int(d[key], 0)
166
167 makeint("recovery_api_version")
168 makeint("blocksize")
169 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700170 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700171 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700172 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700173 makeint("recovery_size")
174 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800175 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700176
Doug Zongkerc9253822014-02-04 12:17:58 -0800177 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
178 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700179 return d
180
Doug Zongkerc9253822014-02-04 12:17:58 -0800181def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700182 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800183 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700184 except KeyError:
185 print "Warning: could not find SYSTEM/build.prop in %s" % zip
186 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700187 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700188
Michael Runge6e836112014-04-15 17:40:21 -0700189def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700190 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700191 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700192 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700193 if not line or line.startswith("#"):
194 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700195 if "=" in line:
196 name, value = line.split("=", 1)
197 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700198 return d
199
Doug Zongkerc9253822014-02-04 12:17:58 -0800200def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700201 class Partition(object):
Tao Baodf06e962015-06-10 12:32:41 -0700202 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700203 self.mount_point = mount_point
204 self.fs_type = fs_type
205 self.device = device
206 self.length = length
207 self.device2 = device2
Tao Baodf06e962015-06-10 12:32:41 -0700208 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700209
210 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800211 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700212 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800213 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700214 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700215
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800216 if fstab_version == 1:
217 d = {}
218 for line in data.split("\n"):
219 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700220 if not line or line.startswith("#"):
221 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800222 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700223 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800224 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800225 options = None
226 if len(pieces) >= 4:
227 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700228 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800229 if len(pieces) >= 5:
230 options = pieces[4]
231 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700232 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800233 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800234 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700235 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700236
Dan Albert8b72aef2015-03-23 19:13:21 -0700237 mount_point = pieces[0]
238 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800239 if options:
240 options = options.split(",")
241 for i in options:
242 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700243 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800244 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700245 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800246
Dan Albert8b72aef2015-03-23 19:13:21 -0700247 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
248 device=pieces[2], length=length,
249 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800250
251 elif fstab_version == 2:
252 d = {}
253 for line in data.split("\n"):
254 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700255 if not line or line.startswith("#"):
256 continue
Tao Baodf06e962015-06-10 12:32:41 -0700257 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800258 pieces = line.split()
259 if len(pieces) != 5:
260 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
261
262 # Ignore entries that are managed by vold
263 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700264 if "voldmanaged=" in options:
265 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800266
267 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700268 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800269 options = options.split(",")
270 for i in options:
271 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700272 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800273 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800274 # Ignore all unknown options in the unified fstab
275 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800276
Tao Baodf06e962015-06-10 12:32:41 -0700277 mount_flags = pieces[3]
278 # Honor the SELinux context if present.
279 context = None
280 for i in mount_flags.split(","):
281 if i.startswith("context="):
282 context = i
283
Dan Albert8b72aef2015-03-23 19:13:21 -0700284 mount_point = pieces[1]
285 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Baodf06e962015-06-10 12:32:41 -0700286 device=pieces[0], length=length,
287 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800288
289 else:
290 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
291
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700292 return d
293
294
Doug Zongker37974732010-09-16 17:44:38 -0700295def DumpInfoDict(d):
296 for k, v in sorted(d.items()):
297 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700298
Dan Albert8b72aef2015-03-23 19:13:21 -0700299
Doug Zongkerd5131602012-08-02 14:46:42 -0700300def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700301 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700302 'sourcedir'), and turn them into a boot image. Return the image
303 data, or None if sourcedir does not appear to contains files for
304 building the requested image."""
305
306 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
307 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
308 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700309
Doug Zongkerd5131602012-08-02 14:46:42 -0700310 if info_dict is None:
311 info_dict = OPTIONS.info_dict
312
Doug Zongkereef39442009-04-02 12:14:19 -0700313 ramdisk_img = tempfile.NamedTemporaryFile()
314 img = tempfile.NamedTemporaryFile()
315
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700316 if os.access(fs_config_file, os.F_OK):
317 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
318 else:
319 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
320 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700321 p2 = Run(["minigzip"],
322 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700323
324 p2.wait()
325 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700326 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
327 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700328
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800329 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
330 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
331
332 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700333
Benoit Fradina45a8682014-07-14 21:00:43 +0200334 fn = os.path.join(sourcedir, "second")
335 if os.access(fn, os.F_OK):
336 cmd.append("--second")
337 cmd.append(fn)
338
Doug Zongker171f1cd2009-06-15 22:36:37 -0700339 fn = os.path.join(sourcedir, "cmdline")
340 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700341 cmd.append("--cmdline")
342 cmd.append(open(fn).read().rstrip("\n"))
343
344 fn = os.path.join(sourcedir, "base")
345 if os.access(fn, os.F_OK):
346 cmd.append("--base")
347 cmd.append(open(fn).read().rstrip("\n"))
348
Ying Wang4de6b5b2010-08-25 14:29:34 -0700349 fn = os.path.join(sourcedir, "pagesize")
350 if os.access(fn, os.F_OK):
351 cmd.append("--pagesize")
352 cmd.append(open(fn).read().rstrip("\n"))
353
Doug Zongkerd5131602012-08-02 14:46:42 -0700354 args = info_dict.get("mkbootimg_args", None)
355 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700356 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700357
Tao Baod95e9fd2015-03-29 23:07:41 -0700358 img_unsigned = None
359 if info_dict.get("vboot", None):
360 img_unsigned = tempfile.NamedTemporaryFile()
361 cmd.extend(["--ramdisk", ramdisk_img.name,
362 "--output", img_unsigned.name])
363 else:
364 cmd.extend(["--ramdisk", ramdisk_img.name,
365 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700366
367 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700368 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700369 assert p.returncode == 0, "mkbootimg of %s image failed" % (
370 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700371
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700372 if info_dict.get("verity_key", None):
373 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin852a5b52014-11-20 09:52:05 -0800374 cmd = [OPTIONS.boot_signer_path, path, img.name,
375 info_dict["verity_key"] + ".pk8",
Dan Albert8b72aef2015-03-23 19:13:21 -0700376 info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700377 p = Run(cmd, stdout=subprocess.PIPE)
378 p.communicate()
379 assert p.returncode == 0, "boot_signer of %s image failed" % path
380
Tao Baod95e9fd2015-03-29 23:07:41 -0700381 # Sign the image if vboot is non-empty.
382 elif info_dict.get("vboot", None):
383 path = "/" + os.path.basename(sourcedir).lower()
384 img_keyblock = tempfile.NamedTemporaryFile()
385 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
386 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
387 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
388 img.name]
389 p = Run(cmd, stdout=subprocess.PIPE)
390 p.communicate()
391 assert p.returncode == 0, "vboot_signer of %s image failed" % path
392
Tao Bao2ed665a2015-04-01 11:21:55 -0700393 # Clean up the temp files.
394 img_unsigned.close()
395 img_keyblock.close()
396
Doug Zongkereef39442009-04-02 12:14:19 -0700397 img.seek(os.SEEK_SET, 0)
398 data = img.read()
399
400 ramdisk_img.close()
401 img.close()
402
403 return data
404
405
Doug Zongkerd5131602012-08-02 14:46:42 -0700406def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
407 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800408 """Return a File object (with name 'name') with the desired bootable
409 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700410 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
411 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800412 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700413
Doug Zongker55d93282011-01-25 17:03:34 -0800414 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
415 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700416 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800417 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700418
419 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
420 if os.path.exists(prebuilt_path):
421 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
422 return File.FromLocalFile(name, prebuilt_path)
423
424 print "building image from target_files %s..." % (tree_subdir,)
425 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
426 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
427 os.path.join(unpack_dir, fs_config),
428 info_dict)
429 if data:
430 return File(name, data)
431 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800432
Doug Zongkereef39442009-04-02 12:14:19 -0700433
Doug Zongker75f17362009-12-08 13:46:44 -0800434def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800435 """Unzip the given archive into a temporary directory and return the name.
436
437 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
438 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
439
440 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
441 main file), open for reading.
442 """
Doug Zongkereef39442009-04-02 12:14:19 -0700443
444 tmp = tempfile.mkdtemp(prefix="targetfiles-")
445 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800446
447 def unzip_to_dir(filename, dirname):
448 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
449 if pattern is not None:
450 cmd.append(pattern)
451 p = Run(cmd, stdout=subprocess.PIPE)
452 p.communicate()
453 if p.returncode != 0:
454 raise ExternalError("failed to unzip input target-files \"%s\"" %
455 (filename,))
456
457 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
458 if m:
459 unzip_to_dir(m.group(1), tmp)
460 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
461 filename = m.group(1)
462 else:
463 unzip_to_dir(filename, tmp)
464
465 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700466
467
468def GetKeyPasswords(keylist):
469 """Given a list of keys, prompt the user to enter passwords for
470 those which require them. Return a {key: password} dict. password
471 will be None if the key has no password."""
472
Doug Zongker8ce7c252009-05-22 13:34:54 -0700473 no_passwords = []
474 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700475 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700476 devnull = open("/dev/null", "w+b")
477 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800478 # We don't need a password for things that aren't really keys.
479 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700480 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700481 continue
482
T.R. Fullhart37e10522013-03-18 10:31:26 -0700483 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700484 "-inform", "DER", "-nocrypt"],
485 stdin=devnull.fileno(),
486 stdout=devnull.fileno(),
487 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700488 p.communicate()
489 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700490 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700491 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700492 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700493 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
494 "-inform", "DER", "-passin", "pass:"],
495 stdin=devnull.fileno(),
496 stdout=devnull.fileno(),
497 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700498 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700499 if p.returncode == 0:
500 # Encrypted key with empty string as password.
501 key_passwords[k] = ''
502 elif stderr.startswith('Error decrypting key'):
503 # Definitely encrypted key.
504 # It would have said "Error reading key" if it didn't parse correctly.
505 need_passwords.append(k)
506 else:
507 # Potentially, a type of key that openssl doesn't understand.
508 # We'll let the routines in signapk.jar handle it.
509 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700510 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700511
T.R. Fullhart37e10522013-03-18 10:31:26 -0700512 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700513 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700514 return key_passwords
515
516
Doug Zongker951495f2009-08-14 12:44:19 -0700517def SignFile(input_name, output_name, key, password, align=None,
518 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700519 """Sign the input_name zip/jar/apk, producing output_name. Use the
520 given key and password (the latter may be None if the key does not
521 have a password.
522
523 If align is an integer > 1, zipalign is run to align stored files in
524 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700525
526 If whole_file is true, use the "-w" option to SignApk to embed a
527 signature that covers the whole file in the archive comment of the
528 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700529 """
Doug Zongker951495f2009-08-14 12:44:19 -0700530
Doug Zongkereef39442009-04-02 12:14:19 -0700531 if align == 0 or align == 1:
532 align = None
533
534 if align:
535 temp = tempfile.NamedTemporaryFile()
536 sign_name = temp.name
537 else:
538 sign_name = output_name
539
Baligh Uddin339ee492014-09-05 11:18:07 -0700540 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700541 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
542 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700543 if whole_file:
544 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700545 cmd.extend([key + OPTIONS.public_key_suffix,
546 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700547 input_name, sign_name])
548
549 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700550 if password is not None:
551 password += "\n"
552 p.communicate(password)
553 if p.returncode != 0:
554 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
555
556 if align:
Brian Carlstrom663127d2015-05-22 15:51:19 -0700557 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700558 p.communicate()
559 if p.returncode != 0:
560 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
561 temp.close()
562
563
Doug Zongker37974732010-09-16 17:44:38 -0700564def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700565 """Check the data string passed against the max size limit, if
566 any, for the given target. Raise exception if the data is too big.
567 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700568
Dan Albert8b72aef2015-03-23 19:13:21 -0700569 if target.endswith(".img"):
570 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700571 mount_point = "/" + target
572
Ying Wangf8824af2014-06-03 14:07:27 -0700573 fs_type = None
574 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700575 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700576 if mount_point == "/userdata":
577 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700578 p = info_dict["fstab"][mount_point]
579 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800580 device = p.device
581 if "/" in device:
582 device = device[device.rfind("/")+1:]
583 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700584 if not fs_type or not limit:
585 return
Doug Zongkereef39442009-04-02 12:14:19 -0700586
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700587 if fs_type == "yaffs2":
588 # image size should be increased by 1/64th to account for the
589 # spare area (64 bytes per 2k page)
590 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800591 size = len(data)
592 pct = float(size) * 100.0 / limit
593 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
594 if pct >= 99.0:
595 raise ExternalError(msg)
596 elif pct >= 95.0:
597 print
598 print " WARNING: ", msg
599 print
600 elif OPTIONS.verbose:
601 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700602
603
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800604def ReadApkCerts(tf_zip):
605 """Given a target_files ZipFile, parse the META/apkcerts.txt file
606 and return a {package: cert} dict."""
607 certmap = {}
608 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
609 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700610 if not line:
611 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800612 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
613 r'private_key="(.*)"$', line)
614 if m:
615 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700616 public_key_suffix_len = len(OPTIONS.public_key_suffix)
617 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800618 if cert in SPECIAL_CERT_STRINGS and not privkey:
619 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700620 elif (cert.endswith(OPTIONS.public_key_suffix) and
621 privkey.endswith(OPTIONS.private_key_suffix) and
622 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
623 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800624 else:
625 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
626 return certmap
627
628
Doug Zongkereef39442009-04-02 12:14:19 -0700629COMMON_DOCSTRING = """
630 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700631 Prepend <dir>/bin to the list of places to search for binaries
632 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700633
Doug Zongker05d3dea2009-06-22 11:32:31 -0700634 -s (--device_specific) <file>
635 Path to the python module containing device-specific
636 releasetools code.
637
Doug Zongker8bec09e2009-11-30 15:37:14 -0800638 -x (--extra) <key=value>
639 Add a key/value pair to the 'extras' dict, which device-specific
640 extension code may look at.
641
Doug Zongkereef39442009-04-02 12:14:19 -0700642 -v (--verbose)
643 Show command lines being executed.
644
645 -h (--help)
646 Display this usage message and exit.
647"""
648
649def Usage(docstring):
650 print docstring.rstrip("\n")
651 print COMMON_DOCSTRING
652
653
654def ParseOptions(argv,
655 docstring,
656 extra_opts="", extra_long_opts=(),
657 extra_option_handler=None):
658 """Parse the options in argv and return any arguments that aren't
659 flags. docstring is the calling module's docstring, to be displayed
660 for errors and -h. extra_opts and extra_long_opts are for flags
661 defined by the caller, which are processed by passing them to
662 extra_option_handler."""
663
664 try:
665 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800666 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700667 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700668 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin852a5b52014-11-20 09:52:05 -0800669 "private_key_suffix=", "boot_signer_path=", "device_specific=",
670 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700671 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700672 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700673 Usage(docstring)
674 print "**", str(err), "**"
675 sys.exit(2)
676
Doug Zongkereef39442009-04-02 12:14:19 -0700677 for o, a in opts:
678 if o in ("-h", "--help"):
679 Usage(docstring)
680 sys.exit()
681 elif o in ("-v", "--verbose"):
682 OPTIONS.verbose = True
683 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700684 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700685 elif o in ("--signapk_path",):
686 OPTIONS.signapk_path = a
687 elif o in ("--extra_signapk_args",):
688 OPTIONS.extra_signapk_args = shlex.split(a)
689 elif o in ("--java_path",):
690 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700691 elif o in ("--java_args",):
692 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700693 elif o in ("--public_key_suffix",):
694 OPTIONS.public_key_suffix = a
695 elif o in ("--private_key_suffix",):
696 OPTIONS.private_key_suffix = a
Baligh Uddin852a5b52014-11-20 09:52:05 -0800697 elif o in ("--boot_signer_path",):
698 OPTIONS.boot_signer_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700699 elif o in ("-s", "--device_specific"):
700 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800701 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800702 key, value = a.split("=", 1)
703 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700704 else:
705 if extra_option_handler is None or not extra_option_handler(o, a):
706 assert False, "unknown option \"%s\"" % (o,)
707
Doug Zongker85448772014-09-09 14:59:20 -0700708 if OPTIONS.search_path:
709 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
710 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700711
712 return args
713
714
Doug Zongkerfc44a512014-08-26 13:10:25 -0700715def MakeTempFile(prefix=None, suffix=None):
716 """Make a temp file and add it to the list of things to be deleted
717 when Cleanup() is called. Return the filename."""
718 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
719 os.close(fd)
720 OPTIONS.tempfiles.append(fn)
721 return fn
722
723
Doug Zongkereef39442009-04-02 12:14:19 -0700724def Cleanup():
725 for i in OPTIONS.tempfiles:
726 if os.path.isdir(i):
727 shutil.rmtree(i)
728 else:
729 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700730
731
732class PasswordManager(object):
733 def __init__(self):
734 self.editor = os.getenv("EDITOR", None)
735 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
736
737 def GetPasswords(self, items):
738 """Get passwords corresponding to each string in 'items',
739 returning a dict. (The dict may have keys in addition to the
740 values in 'items'.)
741
742 Uses the passwords in $ANDROID_PW_FILE if available, letting the
743 user edit that file to add more needed passwords. If no editor is
744 available, or $ANDROID_PW_FILE isn't define, prompts the user
745 interactively in the ordinary way.
746 """
747
748 current = self.ReadFile()
749
750 first = True
751 while True:
752 missing = []
753 for i in items:
754 if i not in current or not current[i]:
755 missing.append(i)
756 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700757 if not missing:
758 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700759
760 for i in missing:
761 current[i] = ""
762
763 if not first:
764 print "key file %s still missing some passwords." % (self.pwfile,)
765 answer = raw_input("try to edit again? [y]> ").strip()
766 if answer and answer[0] not in 'yY':
767 raise RuntimeError("key passwords unavailable")
768 first = False
769
770 current = self.UpdateAndReadFile(current)
771
Dan Albert8b72aef2015-03-23 19:13:21 -0700772 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700773 """Prompt the user to enter a value (password) for each key in
774 'current' whose value is fales. Returns a new dict with all the
775 values.
776 """
777 result = {}
778 for k, v in sorted(current.iteritems()):
779 if v:
780 result[k] = v
781 else:
782 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700783 result[k] = getpass.getpass(
784 "Enter password for %s key> " % k).strip()
785 if result[k]:
786 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700787 return result
788
789 def UpdateAndReadFile(self, current):
790 if not self.editor or not self.pwfile:
791 return self.PromptResult(current)
792
793 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700794 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700795 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
796 f.write("# (Additional spaces are harmless.)\n\n")
797
798 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700799 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
800 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700801 f.write("[[[ %s ]]] %s\n" % (v, k))
802 if not v and first_line is None:
803 # position cursor on first line with no password.
804 first_line = i + 4
805 f.close()
806
807 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
808 _, _ = p.communicate()
809
810 return self.ReadFile()
811
812 def ReadFile(self):
813 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700814 if self.pwfile is None:
815 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700816 try:
817 f = open(self.pwfile, "r")
818 for line in f:
819 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700820 if not line or line[0] == '#':
821 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700822 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
823 if not m:
824 print "failed to parse password file: ", line
825 else:
826 result[m.group(2)] = m.group(1)
827 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700828 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700829 if e.errno != errno.ENOENT:
830 print "error reading password file: ", str(e)
831 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700832
833
Dan Albert8e0178d2015-01-27 15:53:15 -0800834def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
835 compress_type=None):
836 import datetime
837
838 # http://b/18015246
839 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
840 # for files larger than 2GiB. We can work around this by adjusting their
841 # limit. Note that `zipfile.writestr()` will not work for strings larger than
842 # 2GiB. The Python interpreter sometimes rejects strings that large (though
843 # it isn't clear to me exactly what circumstances cause this).
844 # `zipfile.write()` must be used directly to work around this.
845 #
846 # This mess can be avoided if we port to python3.
847 saved_zip64_limit = zipfile.ZIP64_LIMIT
848 zipfile.ZIP64_LIMIT = (1 << 32) - 1
849
850 if compress_type is None:
851 compress_type = zip_file.compression
852 if arcname is None:
853 arcname = filename
854
855 saved_stat = os.stat(filename)
856
857 try:
858 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
859 # file to be zipped and reset it when we're done.
860 os.chmod(filename, perms)
861
862 # Use a fixed timestamp so the output is repeatable.
863 epoch = datetime.datetime.fromtimestamp(0)
864 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
865 os.utime(filename, (timestamp, timestamp))
866
867 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
868 finally:
869 os.chmod(filename, saved_stat.st_mode)
870 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
871 zipfile.ZIP64_LIMIT = saved_zip64_limit
872
873
Tao Bao97734652015-05-20 09:32:18 -0700874def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Bao2ed665a2015-04-01 11:21:55 -0700875 compress_type=None):
876 """Wrap zipfile.writestr() function to work around the zip64 limit.
877
878 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
879 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
880 when calling crc32(bytes).
881
882 But it still works fine to write a shorter string into a large zip file.
883 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
884 when we know the string won't be too long.
885 """
886
887 saved_zip64_limit = zipfile.ZIP64_LIMIT
888 zipfile.ZIP64_LIMIT = (1 << 32) - 1
889
890 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
891 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700892 zinfo.compress_type = zip_file.compression
Tao Bao97734652015-05-20 09:32:18 -0700893 if perms is None:
894 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800895 else:
Tao Bao2ed665a2015-04-01 11:21:55 -0700896 zinfo = zinfo_or_arcname
897
898 # If compress_type is given, it overrides the value in zinfo.
899 if compress_type is not None:
900 zinfo.compress_type = compress_type
901
Tao Bao97734652015-05-20 09:32:18 -0700902 # If perms is given, it has a priority.
903 if perms is not None:
904 zinfo.external_attr = perms << 16
905
Tao Bao2ed665a2015-04-01 11:21:55 -0700906 # Use a fixed timestamp so the output is repeatable.
Tao Bao2ed665a2015-04-01 11:21:55 -0700907 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
908
Dan Albert8b72aef2015-03-23 19:13:21 -0700909 zip_file.writestr(zinfo, data)
Tao Bao2ed665a2015-04-01 11:21:55 -0700910 zipfile.ZIP64_LIMIT = saved_zip64_limit
911
912
913def ZipClose(zip_file):
914 # http://b/18015246
915 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
916 # central directory.
917 saved_zip64_limit = zipfile.ZIP64_LIMIT
918 zipfile.ZIP64_LIMIT = (1 << 32) - 1
919
920 zip_file.close()
921
922 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700923
924
925class DeviceSpecificParams(object):
926 module = None
927 def __init__(self, **kwargs):
928 """Keyword arguments to the constructor become attributes of this
929 object, which is passed to all functions in the device-specific
930 module."""
931 for k, v in kwargs.iteritems():
932 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800933 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700934
935 if self.module is None:
936 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700937 if not path:
938 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700939 try:
940 if os.path.isdir(path):
941 info = imp.find_module("releasetools", [path])
942 else:
943 d, f = os.path.split(path)
944 b, x = os.path.splitext(f)
945 if x == ".py":
946 f = b
947 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800948 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700949 self.module = imp.load_module("device_specific", *info)
950 except ImportError:
951 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700952
953 def _DoCall(self, function_name, *args, **kwargs):
954 """Call the named function in the device-specific module, passing
955 the given args and kwargs. The first argument to the call will be
956 the DeviceSpecific object itself. If there is no module, or the
957 module does not define the function, return the value of the
958 'default' kwarg (which itself defaults to None)."""
959 if self.module is None or not hasattr(self.module, function_name):
960 return kwargs.get("default", None)
961 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
962
963 def FullOTA_Assertions(self):
964 """Called after emitting the block of assertions at the top of a
965 full OTA package. Implementations can add whatever additional
966 assertions they like."""
967 return self._DoCall("FullOTA_Assertions")
968
Doug Zongkere5ff5902012-01-17 10:55:37 -0800969 def FullOTA_InstallBegin(self):
970 """Called at the start of full OTA installation."""
971 return self._DoCall("FullOTA_InstallBegin")
972
Doug Zongker05d3dea2009-06-22 11:32:31 -0700973 def FullOTA_InstallEnd(self):
974 """Called at the end of full OTA installation; typically this is
975 used to install the image for the device's baseband processor."""
976 return self._DoCall("FullOTA_InstallEnd")
977
978 def IncrementalOTA_Assertions(self):
979 """Called after emitting the block of assertions at the top of an
980 incremental OTA package. Implementations can add whatever
981 additional assertions they like."""
982 return self._DoCall("IncrementalOTA_Assertions")
983
Doug Zongkere5ff5902012-01-17 10:55:37 -0800984 def IncrementalOTA_VerifyBegin(self):
985 """Called at the start of the verification phase of incremental
986 OTA installation; additional checks can be placed here to abort
987 the script before any changes are made."""
988 return self._DoCall("IncrementalOTA_VerifyBegin")
989
Doug Zongker05d3dea2009-06-22 11:32:31 -0700990 def IncrementalOTA_VerifyEnd(self):
991 """Called at the end of the verification phase of incremental OTA
992 installation; additional checks can be placed here to abort the
993 script before any changes are made."""
994 return self._DoCall("IncrementalOTA_VerifyEnd")
995
Doug Zongkere5ff5902012-01-17 10:55:37 -0800996 def IncrementalOTA_InstallBegin(self):
997 """Called at the start of incremental OTA installation (after
998 verification is complete)."""
999 return self._DoCall("IncrementalOTA_InstallBegin")
1000
Doug Zongker05d3dea2009-06-22 11:32:31 -07001001 def IncrementalOTA_InstallEnd(self):
1002 """Called at the end of incremental OTA installation; typically
1003 this is used to install the image for the device's baseband
1004 processor."""
1005 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001006
1007class File(object):
1008 def __init__(self, name, data):
1009 self.name = name
1010 self.data = data
1011 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001012 self.sha1 = sha1(data).hexdigest()
1013
1014 @classmethod
1015 def FromLocalFile(cls, name, diskname):
1016 f = open(diskname, "rb")
1017 data = f.read()
1018 f.close()
1019 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001020
1021 def WriteToTemp(self):
1022 t = tempfile.NamedTemporaryFile()
1023 t.write(self.data)
1024 t.flush()
1025 return t
1026
Geremy Condra36bd3652014-02-06 19:45:10 -08001027 def AddToZip(self, z, compression=None):
Tao Bao2ed665a2015-04-01 11:21:55 -07001028 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001029
1030DIFF_PROGRAM_BY_EXT = {
1031 ".gz" : "imgdiff",
1032 ".zip" : ["imgdiff", "-z"],
1033 ".jar" : ["imgdiff", "-z"],
1034 ".apk" : ["imgdiff", "-z"],
1035 ".img" : "imgdiff",
1036 }
1037
1038class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001039 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001040 self.tf = tf
1041 self.sf = sf
1042 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001043 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001044
1045 def ComputePatch(self):
1046 """Compute the patch (as a string of data) needed to turn sf into
1047 tf. Returns the same tuple as GetPatch()."""
1048
1049 tf = self.tf
1050 sf = self.sf
1051
Doug Zongker24cd2802012-08-14 16:36:15 -07001052 if self.diff_program:
1053 diff_program = self.diff_program
1054 else:
1055 ext = os.path.splitext(tf.name)[1]
1056 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001057
1058 ttemp = tf.WriteToTemp()
1059 stemp = sf.WriteToTemp()
1060
1061 ext = os.path.splitext(tf.name)[1]
1062
1063 try:
1064 ptemp = tempfile.NamedTemporaryFile()
1065 if isinstance(diff_program, list):
1066 cmd = copy.copy(diff_program)
1067 else:
1068 cmd = [diff_program]
1069 cmd.append(stemp.name)
1070 cmd.append(ttemp.name)
1071 cmd.append(ptemp.name)
1072 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001073 err = []
1074 def run():
1075 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001076 if e:
1077 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001078 th = threading.Thread(target=run)
1079 th.start()
1080 th.join(timeout=300) # 5 mins
1081 if th.is_alive():
1082 print "WARNING: diff command timed out"
1083 p.terminate()
1084 th.join(5)
1085 if th.is_alive():
1086 p.kill()
1087 th.join()
1088
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001089 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001090 print "WARNING: failure running %s:\n%s\n" % (
1091 diff_program, "".join(err))
1092 self.patch = None
1093 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001094 diff = ptemp.read()
1095 finally:
1096 ptemp.close()
1097 stemp.close()
1098 ttemp.close()
1099
1100 self.patch = diff
1101 return self.tf, self.sf, self.patch
1102
1103
1104 def GetPatch(self):
1105 """Return a tuple (target_file, source_file, patch_data).
1106 patch_data may be None if ComputePatch hasn't been called, or if
1107 computing the patch failed."""
1108 return self.tf, self.sf, self.patch
1109
1110
1111def ComputeDifferences(diffs):
1112 """Call ComputePatch on all the Difference objects in 'diffs'."""
1113 print len(diffs), "diffs to compute"
1114
1115 # Do the largest files first, to try and reduce the long-pole effect.
1116 by_size = [(i.tf.size, i) for i in diffs]
1117 by_size.sort(reverse=True)
1118 by_size = [i[1] for i in by_size]
1119
1120 lock = threading.Lock()
1121 diff_iter = iter(by_size) # accessed under lock
1122
1123 def worker():
1124 try:
1125 lock.acquire()
1126 for d in diff_iter:
1127 lock.release()
1128 start = time.time()
1129 d.ComputePatch()
1130 dur = time.time() - start
1131 lock.acquire()
1132
1133 tf, sf, patch = d.GetPatch()
1134 if sf.name == tf.name:
1135 name = tf.name
1136 else:
1137 name = "%s (%s)" % (tf.name, sf.name)
1138 if patch is None:
1139 print "patching failed! %s" % (name,)
1140 else:
1141 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1142 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1143 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001144 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001145 print e
1146 raise
1147
1148 # start worker threads; wait for them all to finish.
1149 threads = [threading.Thread(target=worker)
1150 for i in range(OPTIONS.worker_threads)]
1151 for th in threads:
1152 th.start()
1153 while threads:
1154 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001155
1156
Dan Albert8b72aef2015-03-23 19:13:21 -07001157class BlockDifference(object):
1158 def __init__(self, partition, tgt, src=None, check_first_block=False,
1159 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001160 self.tgt = tgt
1161 self.src = src
1162 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001163 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001164
Tao Baoff777812015-05-12 11:42:31 -07001165 # Due to http://b/20939131, check_first_block is disabled temporarily.
1166 assert not self.check_first_block
1167
Tao Baodd2a5892015-03-12 12:32:37 -07001168 if version is None:
1169 version = 1
1170 if OPTIONS.info_dict:
1171 version = max(
1172 int(i) for i in
1173 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1174 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001175
1176 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001177 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001178 tmpdir = tempfile.mkdtemp()
1179 OPTIONS.tempfiles.append(tmpdir)
1180 self.path = os.path.join(tmpdir, partition)
1181 b.Compute(self.path)
1182
1183 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1184
1185 def WriteScript(self, script, output_zip, progress=None):
1186 if not self.src:
1187 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001188 script.Print("Patching %s image unconditionally..." % (self.partition,))
1189 else:
1190 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001191
Dan Albert8b72aef2015-03-23 19:13:21 -07001192 if progress:
1193 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001194 self._WriteUpdate(script, output_zip)
Tao Bao68658c02015-06-01 13:40:49 -07001195 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001196
1197 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001198 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001199 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001200 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001201 else:
Tao Baoff777812015-05-12 11:42:31 -07001202 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1203 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001204 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001205 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1206 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001207 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001208 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baoff777812015-05-12 11:42:31 -07001209 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001210 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001211 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001212 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Baoff777812015-05-12 11:42:31 -07001213 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001214 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001215 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001216
Tao Baodd2a5892015-03-12 12:32:37 -07001217 # When generating incrementals for the system and vendor partitions,
1218 # explicitly check the first block (which contains the superblock) of
1219 # the partition to see if it's what we expect. If this check fails,
1220 # give an explicit log message about the partition having been
1221 # remounted R/W (the most likely explanation) and the need to flash to
1222 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001223 if self.check_first_block:
1224 self._CheckFirstBlock(script)
1225
Tao Baodd2a5892015-03-12 12:32:37 -07001226 # Abort the OTA update. Note that the incremental OTA cannot be applied
1227 # even if it may match the checksum of the target partition.
1228 # a) If version < 3, operations like move and erase will make changes
1229 # unconditionally and damage the partition.
1230 # b) If version >= 3, it won't even reach here.
1231 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1232 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001233
Tao Bao68658c02015-06-01 13:40:49 -07001234 def _WritePostInstallVerifyScript(self, script):
1235 partition = self.partition
1236 script.Print('Verifying the updated %s image...' % (partition,))
1237 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1238 ranges = self.tgt.care_map
1239 ranges_str = ranges.to_string_raw()
1240 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1241 self.device, ranges_str,
1242 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001243
1244 # Bug: 20881595
1245 # Verify that extended blocks are really zeroed out.
1246 if self.tgt.extended:
1247 ranges_str = self.tgt.extended.to_string_raw()
1248 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1249 self.device, ranges_str,
1250 self._HashZeroBlocks(self.tgt.extended.size())))
1251 script.Print('Verified the updated %s image.' % (partition,))
1252 script.AppendExtra(
1253 'else\n'
1254 ' abort("%s partition has unexpected non-zero contents after OTA '
1255 'update");\n'
1256 'endif;' % (partition,))
1257 else:
1258 script.Print('Verified the updated %s image.' % (partition,))
1259
Tao Bao68658c02015-06-01 13:40:49 -07001260 script.AppendExtra(
1261 'else\n'
1262 ' abort("%s partition has unexpected contents after OTA update");\n'
1263 'endif;' % (partition,))
1264
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001265 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001266 ZipWrite(output_zip,
1267 '{}.transfer.list'.format(self.path),
1268 '{}.transfer.list'.format(self.partition))
1269 ZipWrite(output_zip,
1270 '{}.new.dat'.format(self.path),
1271 '{}.new.dat'.format(self.partition))
1272 ZipWrite(output_zip,
1273 '{}.patch.dat'.format(self.path),
1274 '{}.patch.dat'.format(self.partition),
1275 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001276
Dan Albert8e0178d2015-01-27 15:53:15 -08001277 call = ('block_image_update("{device}", '
1278 'package_extract_file("{partition}.transfer.list"), '
1279 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1280 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001281 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001282
Dan Albert8b72aef2015-03-23 19:13:21 -07001283 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001284 data = source.ReadRangeSet(ranges)
1285 ctx = sha1()
1286
1287 for p in data:
1288 ctx.update(p)
1289
1290 return ctx.hexdigest()
1291
Tao Baoe9b61912015-07-09 17:37:49 -07001292 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1293 """Return the hash value for all zero blocks."""
1294 zero_block = '\x00' * 4096
1295 ctx = sha1()
1296 for _ in range(num_blocks):
1297 ctx.update(zero_block)
1298
1299 return ctx.hexdigest()
1300
Tao Baoff777812015-05-12 11:42:31 -07001301 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1302 # remounting R/W. Will change the checking to a finer-grained way to
1303 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001304 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001305 r = rangelib.RangeSet((0, 1))
1306 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001307
1308 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1309 'abort("%s has been remounted R/W; '
1310 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001311 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001312 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001313
1314DataImage = blockimgdiff.DataImage
1315
1316
Doug Zongker96a57e72010-09-26 14:57:41 -07001317# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001318PARTITION_TYPES = {
1319 "yaffs2": "MTD",
1320 "mtd": "MTD",
1321 "ext4": "EMMC",
1322 "emmc": "EMMC",
Mohamad Ayyasha9905342015-05-01 15:39:36 -07001323 "f2fs": "EMMC",
1324 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001325}
Doug Zongker96a57e72010-09-26 14:57:41 -07001326
1327def GetTypeAndDevice(mount_point, info):
1328 fstab = info["fstab"]
1329 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001330 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1331 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001332 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001333 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001334
1335
1336def ParseCertificate(data):
1337 """Parse a PEM-format certificate."""
1338 cert = []
1339 save = False
1340 for line in data.split("\n"):
1341 if "--END CERTIFICATE--" in line:
1342 break
1343 if save:
1344 cert.append(line)
1345 if "--BEGIN CERTIFICATE--" in line:
1346 save = True
1347 cert = "".join(cert).decode('base64')
1348 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001349
Doug Zongker412c02f2014-02-13 10:58:24 -08001350def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1351 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001352 """Generate a binary patch that creates the recovery image starting
1353 with the boot image. (Most of the space in these images is just the
1354 kernel, which is identical for the two, so the resulting patch
1355 should be efficient.) Add it to the output zip, along with a shell
1356 script that is run from init.rc on first boot to actually do the
1357 patching and install the new recovery image.
1358
1359 recovery_img and boot_img should be File objects for the
1360 corresponding images. info should be the dictionary returned by
1361 common.LoadInfoDict() on the input target_files.
1362 """
1363
Doug Zongker412c02f2014-02-13 10:58:24 -08001364 if info_dict is None:
1365 info_dict = OPTIONS.info_dict
1366
Doug Zongkerc9253822014-02-04 12:17:58 -08001367 diff_program = ["imgdiff"]
1368 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1369 if os.path.exists(path):
1370 diff_program.append("-b")
1371 diff_program.append(path)
1372 bonus_args = "-b /system/etc/recovery-resource.dat"
1373 else:
1374 bonus_args = ""
1375
1376 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1377 _, _, patch = d.ComputePatch()
1378 output_sink("recovery-from-boot.p", patch)
1379
Dan Albertebb19aa2015-03-27 19:11:53 -07001380 try:
1381 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1382 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1383 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001384 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001385
1386 sh = """#!/system/bin/sh
1387if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1388 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"
1389else
1390 log -t recovery "Recovery image already installed"
1391fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001392""" % {'boot_size': boot_img.size,
1393 'boot_sha1': boot_img.sha1,
1394 'recovery_size': recovery_img.size,
1395 'recovery_sha1': recovery_img.sha1,
1396 'boot_type': boot_type,
1397 'boot_device': boot_device,
1398 'recovery_type': recovery_type,
1399 'recovery_device': recovery_device,
1400 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001401
1402 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001403 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001404 # target-files expects it to be, and put it there.
1405 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001406 found = False
1407 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
1408 init_rc_files = os.listdir(init_rc_dir)
1409 for init_rc_file in init_rc_files:
1410 if (not init_rc_file.startswith('init.') or
1411 not init_rc_file.endswith('.rc')):
1412 continue
1413
1414 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001415 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001416 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001417 if m:
1418 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001419 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001420 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001421
1422 if found:
1423 break
1424
1425 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001426
1427 output_sink(sh_location, sh)