blob: cdb5b7f656f0c61affebef42c0e168ebc55c2b2f [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
62 self.worker_threads = None
63
64
65OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070066
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080067
68# Values for "certificate" in apkcerts that mean special things.
69SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
70
71
Dan Albert8b72aef2015-03-23 19:13:21 -070072class ExternalError(RuntimeError):
73 pass
Doug Zongkereef39442009-04-02 12:14:19 -070074
75
76def Run(args, **kwargs):
77 """Create and return a subprocess.Popen object, printing the command
78 line on the terminal if -v was specified."""
79 if OPTIONS.verbose:
80 print " running: ", " ".join(args)
81 return subprocess.Popen(args, **kwargs)
82
83
Ying Wang7e6d4e42010-12-13 16:25:36 -080084def CloseInheritedPipes():
85 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
86 before doing other work."""
87 if platform.system() != "Darwin":
88 return
89 for d in range(3, 1025):
90 try:
91 stat = os.fstat(d)
92 if stat is not None:
93 pipebit = stat[0] & 0x1000
94 if pipebit != 0:
95 os.close(d)
96 except OSError:
97 pass
98
99
Dan Albert8b72aef2015-03-23 19:13:21 -0700100def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700101 """Read and parse the META/misc_info.txt key/value pairs from the
102 input target files and return a dict."""
103
Doug Zongkerc9253822014-02-04 12:17:58 -0800104 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700105 if isinstance(input_file, zipfile.ZipFile):
106 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800107 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700108 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800109 try:
110 with open(path) as f:
111 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700112 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800113 if e.errno == errno.ENOENT:
114 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700115 d = {}
116 try:
Michael Runge6e836112014-04-15 17:40:21 -0700117 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700118 except KeyError:
119 # ok if misc_info.txt doesn't exist
120 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700121
Doug Zongker37974732010-09-16 17:44:38 -0700122 # backwards compatibility: These values used to be in their own
123 # files. Look for them, in case we're processing an old
124 # target_files zip.
125
126 if "mkyaffs2_extra_flags" not in d:
127 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700128 d["mkyaffs2_extra_flags"] = read_helper(
129 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700130 except KeyError:
131 # ok if flags don't exist
132 pass
133
134 if "recovery_api_version" not in d:
135 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700136 d["recovery_api_version"] = read_helper(
137 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700138 except KeyError:
139 raise ValueError("can't find recovery API version in input target-files")
140
141 if "tool_extensions" not in d:
142 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800143 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700144 except KeyError:
145 # ok if extensions don't exist
146 pass
147
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800148 if "fstab_version" not in d:
149 d["fstab_version"] = "1"
150
Doug Zongker37974732010-09-16 17:44:38 -0700151 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800152 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700153 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700154 if not line:
155 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700156 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700157 if not value:
158 continue
Doug Zongker37974732010-09-16 17:44:38 -0700159 if name == "blocksize":
160 d[name] = value
161 else:
162 d[name + "_size"] = value
163 except KeyError:
164 pass
165
166 def makeint(key):
167 if key in d:
168 d[key] = int(d[key], 0)
169
170 makeint("recovery_api_version")
171 makeint("blocksize")
172 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700173 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700174 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700175 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700176 makeint("recovery_size")
177 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800178 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700179
Doug Zongkerc9253822014-02-04 12:17:58 -0800180 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
181 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700182 return d
183
Doug Zongkerc9253822014-02-04 12:17:58 -0800184def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700185 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800186 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700187 except KeyError:
188 print "Warning: could not find SYSTEM/build.prop in %s" % zip
189 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700190 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700191
Michael Runge6e836112014-04-15 17:40:21 -0700192def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700193 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700194 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700195 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700196 if not line or line.startswith("#"):
197 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700198 if "=" in line:
199 name, value = line.split("=", 1)
200 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700201 return d
202
Doug Zongkerc9253822014-02-04 12:17:58 -0800203def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700204 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700205 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700206 self.mount_point = mount_point
207 self.fs_type = fs_type
208 self.device = device
209 self.length = length
210 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700211 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700212
213 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800214 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700215 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800216 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700217 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700218
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800219 if fstab_version == 1:
220 d = {}
221 for line in data.split("\n"):
222 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700223 if not line or line.startswith("#"):
224 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800225 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700226 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800227 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800228 options = None
229 if len(pieces) >= 4:
230 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800232 if len(pieces) >= 5:
233 options = pieces[4]
234 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700235 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800236 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800237 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700238 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700239
Dan Albert8b72aef2015-03-23 19:13:21 -0700240 mount_point = pieces[0]
241 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800242 if options:
243 options = options.split(",")
244 for i in options:
245 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700246 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800247 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700248 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800249
Dan Albert8b72aef2015-03-23 19:13:21 -0700250 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
251 device=pieces[2], length=length,
252 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800253
254 elif fstab_version == 2:
255 d = {}
256 for line in data.split("\n"):
257 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700258 if not line or line.startswith("#"):
259 continue
Tao Bao548eb762015-06-10 12:32:41 -0700260 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800261 pieces = line.split()
262 if len(pieces) != 5:
263 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
264
265 # Ignore entries that are managed by vold
266 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700267 if "voldmanaged=" in options:
268 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800269
270 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700271 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800272 options = options.split(",")
273 for i in options:
274 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700275 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800276 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800277 # Ignore all unknown options in the unified fstab
278 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800279
Tao Bao548eb762015-06-10 12:32:41 -0700280 mount_flags = pieces[3]
281 # Honor the SELinux context if present.
282 context = None
283 for i in mount_flags.split(","):
284 if i.startswith("context="):
285 context = i
286
Dan Albert8b72aef2015-03-23 19:13:21 -0700287 mount_point = pieces[1]
288 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700289 device=pieces[0], length=length,
290 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800291
292 else:
293 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
294
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700295 return d
296
297
Doug Zongker37974732010-09-16 17:44:38 -0700298def DumpInfoDict(d):
299 for k, v in sorted(d.items()):
300 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700301
Dan Albert8b72aef2015-03-23 19:13:21 -0700302
Doug Zongkerd5131602012-08-02 14:46:42 -0700303def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700304 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700305 'sourcedir'), and turn them into a boot image. Return the image
306 data, or None if sourcedir does not appear to contains files for
307 building the requested image."""
308
309 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
310 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
311 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700312
Doug Zongkerd5131602012-08-02 14:46:42 -0700313 if info_dict is None:
314 info_dict = OPTIONS.info_dict
315
Doug Zongkereef39442009-04-02 12:14:19 -0700316 ramdisk_img = tempfile.NamedTemporaryFile()
317 img = tempfile.NamedTemporaryFile()
318
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700319 if os.access(fs_config_file, os.F_OK):
320 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
321 else:
322 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
323 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700324 p2 = Run(["minigzip"],
325 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700326
327 p2.wait()
328 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700329 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
330 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700331
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000332 """check if uboot is requested"""
333 fn = os.path.join(sourcedir, "ubootargs")
Benoit Fradina45a8682014-07-14 21:00:43 +0200334 if os.access(fn, os.F_OK):
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000335 cmd = ["mkimage"]
336 for argument in open(fn).read().rstrip("\n").split(" "):
337 cmd.append(argument)
338 cmd.append("-d")
339 cmd.append(os.path.join(sourcedir, "kernel")+":"+ramdisk_img.name)
340 cmd.append(img.name)
Benoit Fradina45a8682014-07-14 21:00:43 +0200341
Tao Baod95e9fd2015-03-29 23:07:41 -0700342 else:
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000343 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
344 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
345 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700346
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000347 fn = os.path.join(sourcedir, "second")
348 if os.access(fn, os.F_OK):
349 cmd.append("--second")
350 cmd.append(fn)
351
352 fn = os.path.join(sourcedir, "cmdline")
353 if os.access(fn, os.F_OK):
354 cmd.append("--cmdline")
355 cmd.append(open(fn).read().rstrip("\n"))
356
357 fn = os.path.join(sourcedir, "base")
358 if os.access(fn, os.F_OK):
359 cmd.append("--base")
360 cmd.append(open(fn).read().rstrip("\n"))
361
362 fn = os.path.join(sourcedir, "tagsaddr")
363 if os.access(fn, os.F_OK):
364 cmd.append("--tags-addr")
365 cmd.append(open(fn).read().rstrip("\n"))
366
367 fn = os.path.join(sourcedir, "ramdisk_offset")
368 if os.access(fn, os.F_OK):
369 cmd.append("--ramdisk_offset")
370 cmd.append(open(fn).read().rstrip("\n"))
371
372 fn = os.path.join(sourcedir, "dt_args")
373 if os.access(fn, os.F_OK):
374 cmd.append("--dt")
375 cmd.append(open(fn).read().rstrip("\n"))
376
377 fn = os.path.join(sourcedir, "pagesize")
378 if os.access(fn, os.F_OK):
379 cmd.append("--pagesize")
380 cmd.append(open(fn).read().rstrip("\n"))
381
382 args = info_dict.get("mkbootimg_args", None)
383 if args and args.strip():
384 cmd.extend(shlex.split(args))
385
386 img_unsigned = None
387 if info_dict.get("vboot", None):
388 img_unsigned = tempfile.NamedTemporaryFile()
389 cmd.extend(["--ramdisk", ramdisk_img.name,
390 "--output", img_unsigned.name])
391 else:
392 cmd.extend(["--ramdisk", ramdisk_img.name,
393 "--output", img.name])
394
Doug Zongker38a649f2009-06-17 09:07:09 -0700395 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700396 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700397 assert p.returncode == 0, "mkbootimg of %s image failed" % (
398 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700399
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100400 if (info_dict.get("boot_signer", None) == "true" and
401 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700402 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700403 cmd = [OPTIONS.boot_signer_path]
404 cmd.extend(OPTIONS.boot_signer_args)
405 cmd.extend([path, img.name,
406 info_dict["verity_key"] + ".pk8",
407 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700408 p = Run(cmd, stdout=subprocess.PIPE)
409 p.communicate()
410 assert p.returncode == 0, "boot_signer of %s image failed" % path
411
Tao Baod95e9fd2015-03-29 23:07:41 -0700412 # Sign the image if vboot is non-empty.
413 elif info_dict.get("vboot", None):
414 path = "/" + os.path.basename(sourcedir).lower()
415 img_keyblock = tempfile.NamedTemporaryFile()
416 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
417 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
418 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
419 img.name]
420 p = Run(cmd, stdout=subprocess.PIPE)
421 p.communicate()
422 assert p.returncode == 0, "vboot_signer of %s image failed" % path
423
Tao Baof3282b42015-04-01 11:21:55 -0700424 # Clean up the temp files.
425 img_unsigned.close()
426 img_keyblock.close()
427
Doug Zongkereef39442009-04-02 12:14:19 -0700428 img.seek(os.SEEK_SET, 0)
429 data = img.read()
430
431 ramdisk_img.close()
432 img.close()
433
434 return data
435
436
Doug Zongkerd5131602012-08-02 14:46:42 -0700437def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
438 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800439 """Return a File object (with name 'name') with the desired bootable
440 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700441 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
442 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800443 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700444
Doug Zongker55d93282011-01-25 17:03:34 -0800445 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
446 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700447 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800448 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700449
450 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
451 if os.path.exists(prebuilt_path):
452 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
453 return File.FromLocalFile(name, prebuilt_path)
454
455 print "building image from target_files %s..." % (tree_subdir,)
456 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
457 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
458 os.path.join(unpack_dir, fs_config),
459 info_dict)
460 if data:
461 return File(name, data)
462 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800463
Doug Zongkereef39442009-04-02 12:14:19 -0700464
Doug Zongker75f17362009-12-08 13:46:44 -0800465def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800466 """Unzip the given archive into a temporary directory and return the name.
467
468 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
469 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
470
471 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
472 main file), open for reading.
473 """
Doug Zongkereef39442009-04-02 12:14:19 -0700474
475 tmp = tempfile.mkdtemp(prefix="targetfiles-")
476 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800477
478 def unzip_to_dir(filename, dirname):
479 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
480 if pattern is not None:
481 cmd.append(pattern)
482 p = Run(cmd, stdout=subprocess.PIPE)
483 p.communicate()
484 if p.returncode != 0:
485 raise ExternalError("failed to unzip input target-files \"%s\"" %
486 (filename,))
487
488 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
489 if m:
490 unzip_to_dir(m.group(1), tmp)
491 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
492 filename = m.group(1)
493 else:
494 unzip_to_dir(filename, tmp)
495
496 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700497
498
499def GetKeyPasswords(keylist):
500 """Given a list of keys, prompt the user to enter passwords for
501 those which require them. Return a {key: password} dict. password
502 will be None if the key has no password."""
503
Doug Zongker8ce7c252009-05-22 13:34:54 -0700504 no_passwords = []
505 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700506 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700507 devnull = open("/dev/null", "w+b")
508 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800509 # We don't need a password for things that aren't really keys.
510 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700511 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700512 continue
513
T.R. Fullhart37e10522013-03-18 10:31:26 -0700514 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700515 "-inform", "DER", "-nocrypt"],
516 stdin=devnull.fileno(),
517 stdout=devnull.fileno(),
518 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700519 p.communicate()
520 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700521 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700522 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700523 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700524 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
525 "-inform", "DER", "-passin", "pass:"],
526 stdin=devnull.fileno(),
527 stdout=devnull.fileno(),
528 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700529 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700530 if p.returncode == 0:
531 # Encrypted key with empty string as password.
532 key_passwords[k] = ''
533 elif stderr.startswith('Error decrypting key'):
534 # Definitely encrypted key.
535 # It would have said "Error reading key" if it didn't parse correctly.
536 need_passwords.append(k)
537 else:
538 # Potentially, a type of key that openssl doesn't understand.
539 # We'll let the routines in signapk.jar handle it.
540 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700541 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700542
T.R. Fullhart37e10522013-03-18 10:31:26 -0700543 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700544 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700545 return key_passwords
546
547
Doug Zongker951495f2009-08-14 12:44:19 -0700548def SignFile(input_name, output_name, key, password, align=None,
549 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700550 """Sign the input_name zip/jar/apk, producing output_name. Use the
551 given key and password (the latter may be None if the key does not
552 have a password.
553
554 If align is an integer > 1, zipalign is run to align stored files in
555 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700556
557 If whole_file is true, use the "-w" option to SignApk to embed a
558 signature that covers the whole file in the archive comment of the
559 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700560 """
Doug Zongker951495f2009-08-14 12:44:19 -0700561
Doug Zongkereef39442009-04-02 12:14:19 -0700562 if align == 0 or align == 1:
563 align = None
564
565 if align:
566 temp = tempfile.NamedTemporaryFile()
567 sign_name = temp.name
568 else:
569 sign_name = output_name
570
Baligh Uddin339ee492014-09-05 11:18:07 -0700571 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700572 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
573 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700574 if whole_file:
575 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700576 cmd.extend([key + OPTIONS.public_key_suffix,
577 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700578 input_name, sign_name])
579
580 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700581 if password is not None:
582 password += "\n"
583 p.communicate(password)
584 if p.returncode != 0:
585 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
586
587 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700588 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700589 p.communicate()
590 if p.returncode != 0:
591 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
592 temp.close()
593
594
Doug Zongker37974732010-09-16 17:44:38 -0700595def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700596 """Check the data string passed against the max size limit, if
597 any, for the given target. Raise exception if the data is too big.
598 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700599
Dan Albert8b72aef2015-03-23 19:13:21 -0700600 if target.endswith(".img"):
601 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700602 mount_point = "/" + target
603
Ying Wangf8824af2014-06-03 14:07:27 -0700604 fs_type = None
605 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700606 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700607 if mount_point == "/userdata":
608 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700609 p = info_dict["fstab"][mount_point]
610 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800611 device = p.device
612 if "/" in device:
613 device = device[device.rfind("/")+1:]
614 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700615 if not fs_type or not limit:
616 return
Doug Zongkereef39442009-04-02 12:14:19 -0700617
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700618 if fs_type == "yaffs2":
619 # image size should be increased by 1/64th to account for the
620 # spare area (64 bytes per 2k page)
621 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800622 size = len(data)
623 pct = float(size) * 100.0 / limit
624 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
625 if pct >= 99.0:
626 raise ExternalError(msg)
627 elif pct >= 95.0:
628 print
629 print " WARNING: ", msg
630 print
631 elif OPTIONS.verbose:
632 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700633
634
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800635def ReadApkCerts(tf_zip):
636 """Given a target_files ZipFile, parse the META/apkcerts.txt file
637 and return a {package: cert} dict."""
638 certmap = {}
639 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
640 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700641 if not line:
642 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800643 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
644 r'private_key="(.*)"$', line)
645 if m:
646 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700647 public_key_suffix_len = len(OPTIONS.public_key_suffix)
648 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800649 if cert in SPECIAL_CERT_STRINGS and not privkey:
650 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700651 elif (cert.endswith(OPTIONS.public_key_suffix) and
652 privkey.endswith(OPTIONS.private_key_suffix) and
653 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
654 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800655 else:
656 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
657 return certmap
658
659
Doug Zongkereef39442009-04-02 12:14:19 -0700660COMMON_DOCSTRING = """
661 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700662 Prepend <dir>/bin to the list of places to search for binaries
663 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700664
Doug Zongker05d3dea2009-06-22 11:32:31 -0700665 -s (--device_specific) <file>
666 Path to the python module containing device-specific
667 releasetools code.
668
Doug Zongker8bec09e2009-11-30 15:37:14 -0800669 -x (--extra) <key=value>
670 Add a key/value pair to the 'extras' dict, which device-specific
671 extension code may look at.
672
Doug Zongkereef39442009-04-02 12:14:19 -0700673 -v (--verbose)
674 Show command lines being executed.
675
676 -h (--help)
677 Display this usage message and exit.
678"""
679
680def Usage(docstring):
681 print docstring.rstrip("\n")
682 print COMMON_DOCSTRING
683
684
685def ParseOptions(argv,
686 docstring,
687 extra_opts="", extra_long_opts=(),
688 extra_option_handler=None):
689 """Parse the options in argv and return any arguments that aren't
690 flags. docstring is the calling module's docstring, to be displayed
691 for errors and -h. extra_opts and extra_long_opts are for flags
692 defined by the caller, which are processed by passing them to
693 extra_option_handler."""
694
695 try:
696 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800697 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700698 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700699 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700700 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
701 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800702 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700703 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700704 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700705 Usage(docstring)
706 print "**", str(err), "**"
707 sys.exit(2)
708
Doug Zongkereef39442009-04-02 12:14:19 -0700709 for o, a in opts:
710 if o in ("-h", "--help"):
711 Usage(docstring)
712 sys.exit()
713 elif o in ("-v", "--verbose"):
714 OPTIONS.verbose = True
715 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700716 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700717 elif o in ("--signapk_path",):
718 OPTIONS.signapk_path = a
719 elif o in ("--extra_signapk_args",):
720 OPTIONS.extra_signapk_args = shlex.split(a)
721 elif o in ("--java_path",):
722 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700723 elif o in ("--java_args",):
724 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700725 elif o in ("--public_key_suffix",):
726 OPTIONS.public_key_suffix = a
727 elif o in ("--private_key_suffix",):
728 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800729 elif o in ("--boot_signer_path",):
730 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700731 elif o in ("--boot_signer_args",):
732 OPTIONS.boot_signer_args = shlex.split(a)
733 elif o in ("--verity_signer_path",):
734 OPTIONS.verity_signer_path = a
735 elif o in ("--verity_signer_args",):
736 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700737 elif o in ("-s", "--device_specific"):
738 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800739 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800740 key, value = a.split("=", 1)
741 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700742 else:
743 if extra_option_handler is None or not extra_option_handler(o, a):
744 assert False, "unknown option \"%s\"" % (o,)
745
Doug Zongker85448772014-09-09 14:59:20 -0700746 if OPTIONS.search_path:
747 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
748 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700749
750 return args
751
752
Doug Zongkerfc44a512014-08-26 13:10:25 -0700753def MakeTempFile(prefix=None, suffix=None):
754 """Make a temp file and add it to the list of things to be deleted
755 when Cleanup() is called. Return the filename."""
756 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
757 os.close(fd)
758 OPTIONS.tempfiles.append(fn)
759 return fn
760
761
Doug Zongkereef39442009-04-02 12:14:19 -0700762def Cleanup():
763 for i in OPTIONS.tempfiles:
764 if os.path.isdir(i):
765 shutil.rmtree(i)
766 else:
767 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700768
769
770class PasswordManager(object):
771 def __init__(self):
772 self.editor = os.getenv("EDITOR", None)
773 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
774
775 def GetPasswords(self, items):
776 """Get passwords corresponding to each string in 'items',
777 returning a dict. (The dict may have keys in addition to the
778 values in 'items'.)
779
780 Uses the passwords in $ANDROID_PW_FILE if available, letting the
781 user edit that file to add more needed passwords. If no editor is
782 available, or $ANDROID_PW_FILE isn't define, prompts the user
783 interactively in the ordinary way.
784 """
785
786 current = self.ReadFile()
787
788 first = True
789 while True:
790 missing = []
791 for i in items:
792 if i not in current or not current[i]:
793 missing.append(i)
794 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700795 if not missing:
796 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700797
798 for i in missing:
799 current[i] = ""
800
801 if not first:
802 print "key file %s still missing some passwords." % (self.pwfile,)
803 answer = raw_input("try to edit again? [y]> ").strip()
804 if answer and answer[0] not in 'yY':
805 raise RuntimeError("key passwords unavailable")
806 first = False
807
808 current = self.UpdateAndReadFile(current)
809
Dan Albert8b72aef2015-03-23 19:13:21 -0700810 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700811 """Prompt the user to enter a value (password) for each key in
812 'current' whose value is fales. Returns a new dict with all the
813 values.
814 """
815 result = {}
816 for k, v in sorted(current.iteritems()):
817 if v:
818 result[k] = v
819 else:
820 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700821 result[k] = getpass.getpass(
822 "Enter password for %s key> " % k).strip()
823 if result[k]:
824 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700825 return result
826
827 def UpdateAndReadFile(self, current):
828 if not self.editor or not self.pwfile:
829 return self.PromptResult(current)
830
831 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700832 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700833 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
834 f.write("# (Additional spaces are harmless.)\n\n")
835
836 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700837 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
838 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700839 f.write("[[[ %s ]]] %s\n" % (v, k))
840 if not v and first_line is None:
841 # position cursor on first line with no password.
842 first_line = i + 4
843 f.close()
844
845 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
846 _, _ = p.communicate()
847
848 return self.ReadFile()
849
850 def ReadFile(self):
851 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700852 if self.pwfile is None:
853 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700854 try:
855 f = open(self.pwfile, "r")
856 for line in f:
857 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700858 if not line or line[0] == '#':
859 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700860 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
861 if not m:
862 print "failed to parse password file: ", line
863 else:
864 result[m.group(2)] = m.group(1)
865 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700866 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700867 if e.errno != errno.ENOENT:
868 print "error reading password file: ", str(e)
869 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700870
871
Dan Albert8e0178d2015-01-27 15:53:15 -0800872def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
873 compress_type=None):
874 import datetime
875
876 # http://b/18015246
877 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
878 # for files larger than 2GiB. We can work around this by adjusting their
879 # limit. Note that `zipfile.writestr()` will not work for strings larger than
880 # 2GiB. The Python interpreter sometimes rejects strings that large (though
881 # it isn't clear to me exactly what circumstances cause this).
882 # `zipfile.write()` must be used directly to work around this.
883 #
884 # This mess can be avoided if we port to python3.
885 saved_zip64_limit = zipfile.ZIP64_LIMIT
886 zipfile.ZIP64_LIMIT = (1 << 32) - 1
887
888 if compress_type is None:
889 compress_type = zip_file.compression
890 if arcname is None:
891 arcname = filename
892
893 saved_stat = os.stat(filename)
894
895 try:
896 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
897 # file to be zipped and reset it when we're done.
898 os.chmod(filename, perms)
899
900 # Use a fixed timestamp so the output is repeatable.
901 epoch = datetime.datetime.fromtimestamp(0)
902 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
903 os.utime(filename, (timestamp, timestamp))
904
905 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
906 finally:
907 os.chmod(filename, saved_stat.st_mode)
908 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
909 zipfile.ZIP64_LIMIT = saved_zip64_limit
910
911
Tao Bao58c1b962015-05-20 09:32:18 -0700912def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700913 compress_type=None):
914 """Wrap zipfile.writestr() function to work around the zip64 limit.
915
916 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
917 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
918 when calling crc32(bytes).
919
920 But it still works fine to write a shorter string into a large zip file.
921 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
922 when we know the string won't be too long.
923 """
924
925 saved_zip64_limit = zipfile.ZIP64_LIMIT
926 zipfile.ZIP64_LIMIT = (1 << 32) - 1
927
928 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
929 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700930 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700931 if perms is None:
932 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800933 else:
Tao Baof3282b42015-04-01 11:21:55 -0700934 zinfo = zinfo_or_arcname
935
936 # If compress_type is given, it overrides the value in zinfo.
937 if compress_type is not None:
938 zinfo.compress_type = compress_type
939
Tao Bao58c1b962015-05-20 09:32:18 -0700940 # If perms is given, it has a priority.
941 if perms is not None:
942 zinfo.external_attr = perms << 16
943
Tao Baof3282b42015-04-01 11:21:55 -0700944 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700945 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
946
Dan Albert8b72aef2015-03-23 19:13:21 -0700947 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700948 zipfile.ZIP64_LIMIT = saved_zip64_limit
949
950
951def ZipClose(zip_file):
952 # http://b/18015246
953 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
954 # central directory.
955 saved_zip64_limit = zipfile.ZIP64_LIMIT
956 zipfile.ZIP64_LIMIT = (1 << 32) - 1
957
958 zip_file.close()
959
960 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700961
962
963class DeviceSpecificParams(object):
964 module = None
965 def __init__(self, **kwargs):
966 """Keyword arguments to the constructor become attributes of this
967 object, which is passed to all functions in the device-specific
968 module."""
969 for k, v in kwargs.iteritems():
970 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800971 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700972
973 if self.module is None:
974 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700975 if not path:
976 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700977 try:
978 if os.path.isdir(path):
979 info = imp.find_module("releasetools", [path])
980 else:
981 d, f = os.path.split(path)
982 b, x = os.path.splitext(f)
983 if x == ".py":
984 f = b
985 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800986 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700987 self.module = imp.load_module("device_specific", *info)
988 except ImportError:
989 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700990
991 def _DoCall(self, function_name, *args, **kwargs):
992 """Call the named function in the device-specific module, passing
993 the given args and kwargs. The first argument to the call will be
994 the DeviceSpecific object itself. If there is no module, or the
995 module does not define the function, return the value of the
996 'default' kwarg (which itself defaults to None)."""
997 if self.module is None or not hasattr(self.module, function_name):
998 return kwargs.get("default", None)
999 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1000
1001 def FullOTA_Assertions(self):
1002 """Called after emitting the block of assertions at the top of a
1003 full OTA package. Implementations can add whatever additional
1004 assertions they like."""
1005 return self._DoCall("FullOTA_Assertions")
1006
Doug Zongkere5ff5902012-01-17 10:55:37 -08001007 def FullOTA_InstallBegin(self):
1008 """Called at the start of full OTA installation."""
1009 return self._DoCall("FullOTA_InstallBegin")
1010
Doug Zongker05d3dea2009-06-22 11:32:31 -07001011 def FullOTA_InstallEnd(self):
1012 """Called at the end of full OTA installation; typically this is
1013 used to install the image for the device's baseband processor."""
1014 return self._DoCall("FullOTA_InstallEnd")
1015
M1chad27ccf72014-11-25 15:30:48 +01001016 def FullOTA_PostValidate(self):
1017 """Called after installing and validating /system; typically this is
1018 used to resize the system partition after a block based installation."""
1019 return self._DoCall("FullOTA_PostValidate")
1020
Doug Zongker05d3dea2009-06-22 11:32:31 -07001021 def IncrementalOTA_Assertions(self):
1022 """Called after emitting the block of assertions at the top of an
1023 incremental OTA package. Implementations can add whatever
1024 additional assertions they like."""
1025 return self._DoCall("IncrementalOTA_Assertions")
1026
Doug Zongkere5ff5902012-01-17 10:55:37 -08001027 def IncrementalOTA_VerifyBegin(self):
1028 """Called at the start of the verification phase of incremental
1029 OTA installation; additional checks can be placed here to abort
1030 the script before any changes are made."""
1031 return self._DoCall("IncrementalOTA_VerifyBegin")
1032
Doug Zongker05d3dea2009-06-22 11:32:31 -07001033 def IncrementalOTA_VerifyEnd(self):
1034 """Called at the end of the verification phase of incremental OTA
1035 installation; additional checks can be placed here to abort the
1036 script before any changes are made."""
1037 return self._DoCall("IncrementalOTA_VerifyEnd")
1038
Doug Zongkere5ff5902012-01-17 10:55:37 -08001039 def IncrementalOTA_InstallBegin(self):
1040 """Called at the start of incremental OTA installation (after
1041 verification is complete)."""
1042 return self._DoCall("IncrementalOTA_InstallBegin")
1043
Doug Zongker05d3dea2009-06-22 11:32:31 -07001044 def IncrementalOTA_InstallEnd(self):
1045 """Called at the end of incremental OTA installation; typically
1046 this is used to install the image for the device's baseband
1047 processor."""
1048 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001049
1050class File(object):
1051 def __init__(self, name, data):
1052 self.name = name
1053 self.data = data
1054 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001055 self.sha1 = sha1(data).hexdigest()
1056
1057 @classmethod
1058 def FromLocalFile(cls, name, diskname):
1059 f = open(diskname, "rb")
1060 data = f.read()
1061 f.close()
1062 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001063
1064 def WriteToTemp(self):
1065 t = tempfile.NamedTemporaryFile()
1066 t.write(self.data)
1067 t.flush()
1068 return t
1069
Geremy Condra36bd3652014-02-06 19:45:10 -08001070 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001071 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001072
1073DIFF_PROGRAM_BY_EXT = {
1074 ".gz" : "imgdiff",
1075 ".zip" : ["imgdiff", "-z"],
1076 ".jar" : ["imgdiff", "-z"],
1077 ".apk" : ["imgdiff", "-z"],
1078 ".img" : "imgdiff",
1079 }
1080
1081class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001082 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001083 self.tf = tf
1084 self.sf = sf
1085 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001086 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001087
1088 def ComputePatch(self):
1089 """Compute the patch (as a string of data) needed to turn sf into
1090 tf. Returns the same tuple as GetPatch()."""
1091
1092 tf = self.tf
1093 sf = self.sf
1094
Doug Zongker24cd2802012-08-14 16:36:15 -07001095 if self.diff_program:
1096 diff_program = self.diff_program
1097 else:
1098 ext = os.path.splitext(tf.name)[1]
1099 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001100
1101 ttemp = tf.WriteToTemp()
1102 stemp = sf.WriteToTemp()
1103
1104 ext = os.path.splitext(tf.name)[1]
1105
1106 try:
1107 ptemp = tempfile.NamedTemporaryFile()
1108 if isinstance(diff_program, list):
1109 cmd = copy.copy(diff_program)
1110 else:
1111 cmd = [diff_program]
1112 cmd.append(stemp.name)
1113 cmd.append(ttemp.name)
1114 cmd.append(ptemp.name)
1115 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001116 err = []
1117 def run():
1118 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001119 if e:
1120 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001121 th = threading.Thread(target=run)
1122 th.start()
1123 th.join(timeout=300) # 5 mins
1124 if th.is_alive():
1125 print "WARNING: diff command timed out"
1126 p.terminate()
1127 th.join(5)
1128 if th.is_alive():
1129 p.kill()
1130 th.join()
1131
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001132 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001133 print "WARNING: failure running %s:\n%s\n" % (
1134 diff_program, "".join(err))
1135 self.patch = None
1136 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001137 diff = ptemp.read()
1138 finally:
1139 ptemp.close()
1140 stemp.close()
1141 ttemp.close()
1142
1143 self.patch = diff
1144 return self.tf, self.sf, self.patch
1145
1146
1147 def GetPatch(self):
1148 """Return a tuple (target_file, source_file, patch_data).
1149 patch_data may be None if ComputePatch hasn't been called, or if
1150 computing the patch failed."""
1151 return self.tf, self.sf, self.patch
1152
1153
1154def ComputeDifferences(diffs):
1155 """Call ComputePatch on all the Difference objects in 'diffs'."""
1156 print len(diffs), "diffs to compute"
1157
1158 # Do the largest files first, to try and reduce the long-pole effect.
1159 by_size = [(i.tf.size, i) for i in diffs]
1160 by_size.sort(reverse=True)
1161 by_size = [i[1] for i in by_size]
1162
1163 lock = threading.Lock()
1164 diff_iter = iter(by_size) # accessed under lock
1165
1166 def worker():
1167 try:
1168 lock.acquire()
1169 for d in diff_iter:
1170 lock.release()
1171 start = time.time()
1172 d.ComputePatch()
1173 dur = time.time() - start
1174 lock.acquire()
1175
1176 tf, sf, patch = d.GetPatch()
1177 if sf.name == tf.name:
1178 name = tf.name
1179 else:
1180 name = "%s (%s)" % (tf.name, sf.name)
1181 if patch is None:
1182 print "patching failed! %s" % (name,)
1183 else:
1184 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1185 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1186 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001187 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001188 print e
1189 raise
1190
1191 # start worker threads; wait for them all to finish.
1192 threads = [threading.Thread(target=worker)
1193 for i in range(OPTIONS.worker_threads)]
1194 for th in threads:
1195 th.start()
1196 while threads:
1197 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001198
1199
Dan Albert8b72aef2015-03-23 19:13:21 -07001200class BlockDifference(object):
1201 def __init__(self, partition, tgt, src=None, check_first_block=False,
1202 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001203 self.tgt = tgt
1204 self.src = src
1205 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001206 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001207
Tao Bao5ece99d2015-05-12 11:42:31 -07001208 # Due to http://b/20939131, check_first_block is disabled temporarily.
1209 assert not self.check_first_block
1210
Tao Baodd2a5892015-03-12 12:32:37 -07001211 if version is None:
1212 version = 1
1213 if OPTIONS.info_dict:
1214 version = max(
1215 int(i) for i in
1216 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1217 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001218
1219 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001220 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001221 tmpdir = tempfile.mkdtemp()
1222 OPTIONS.tempfiles.append(tmpdir)
1223 self.path = os.path.join(tmpdir, partition)
1224 b.Compute(self.path)
1225
1226 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1227
1228 def WriteScript(self, script, output_zip, progress=None):
1229 if not self.src:
1230 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001231 script.Print("Patching %s image unconditionally..." % (self.partition,))
1232 else:
1233 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001234
Dan Albert8b72aef2015-03-23 19:13:21 -07001235 if progress:
1236 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001237 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001238 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001239
1240 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001241 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001242 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001243 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001244 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001245 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1246 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001247 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001248 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1249 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001250 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001251 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001252 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001253 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001254 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001255 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001256 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001257 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001258 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001259
Tao Baodd2a5892015-03-12 12:32:37 -07001260 # When generating incrementals for the system and vendor partitions,
1261 # explicitly check the first block (which contains the superblock) of
1262 # the partition to see if it's what we expect. If this check fails,
1263 # give an explicit log message about the partition having been
1264 # remounted R/W (the most likely explanation) and the need to flash to
1265 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001266 if self.check_first_block:
1267 self._CheckFirstBlock(script)
1268
Tao Baodd2a5892015-03-12 12:32:37 -07001269 # Abort the OTA update. Note that the incremental OTA cannot be applied
1270 # even if it may match the checksum of the target partition.
1271 # a) If version < 3, operations like move and erase will make changes
1272 # unconditionally and damage the partition.
1273 # b) If version >= 3, it won't even reach here.
1274 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1275 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001276
Tao Bao5fcaaef2015-06-01 13:40:49 -07001277 def _WritePostInstallVerifyScript(self, script):
1278 partition = self.partition
1279 script.Print('Verifying the updated %s image...' % (partition,))
1280 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1281 ranges = self.tgt.care_map
1282 ranges_str = ranges.to_string_raw()
1283 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1284 self.device, ranges_str,
1285 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001286
1287 # Bug: 20881595
1288 # Verify that extended blocks are really zeroed out.
1289 if self.tgt.extended:
1290 ranges_str = self.tgt.extended.to_string_raw()
1291 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1292 self.device, ranges_str,
1293 self._HashZeroBlocks(self.tgt.extended.size())))
1294 script.Print('Verified the updated %s image.' % (partition,))
1295 script.AppendExtra(
1296 'else\n'
1297 ' abort("%s partition has unexpected non-zero contents after OTA '
1298 'update");\n'
1299 'endif;' % (partition,))
1300 else:
1301 script.Print('Verified the updated %s image.' % (partition,))
1302
Tao Bao5fcaaef2015-06-01 13:40:49 -07001303 script.AppendExtra(
1304 'else\n'
1305 ' abort("%s partition has unexpected contents after OTA update");\n'
1306 'endif;' % (partition,))
1307
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001308 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001309 ZipWrite(output_zip,
1310 '{}.transfer.list'.format(self.path),
1311 '{}.transfer.list'.format(self.partition))
1312 ZipWrite(output_zip,
1313 '{}.new.dat'.format(self.path),
1314 '{}.new.dat'.format(self.partition))
1315 ZipWrite(output_zip,
1316 '{}.patch.dat'.format(self.path),
1317 '{}.patch.dat'.format(self.partition),
1318 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001319
Dan Albert8e0178d2015-01-27 15:53:15 -08001320 call = ('block_image_update("{device}", '
1321 'package_extract_file("{partition}.transfer.list"), '
1322 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1323 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001324 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001325
Dan Albert8b72aef2015-03-23 19:13:21 -07001326 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001327 data = source.ReadRangeSet(ranges)
1328 ctx = sha1()
1329
1330 for p in data:
1331 ctx.update(p)
1332
1333 return ctx.hexdigest()
1334
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001335 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1336 """Return the hash value for all zero blocks."""
1337 zero_block = '\x00' * 4096
1338 ctx = sha1()
1339 for _ in range(num_blocks):
1340 ctx.update(zero_block)
1341
1342 return ctx.hexdigest()
1343
Tao Bao5ece99d2015-05-12 11:42:31 -07001344 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1345 # remounting R/W. Will change the checking to a finer-grained way to
1346 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001347 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001348 r = rangelib.RangeSet((0, 1))
1349 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001350
1351 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1352 'abort("%s has been remounted R/W; '
1353 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001354 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001355 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001356
1357DataImage = blockimgdiff.DataImage
1358
1359
Doug Zongker96a57e72010-09-26 14:57:41 -07001360# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001361PARTITION_TYPES = {
1362 "yaffs2": "MTD",
1363 "mtd": "MTD",
1364 "ext4": "EMMC",
1365 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001366 "f2fs": "EMMC",
Brandon Bennett470ea4c2011-11-19 16:02:04 -07001367 "squashfs": "EMMC",
1368 "ext2": "EMMC",
1369 "ext3": "EMMC",
1370 "vfat": "EMMC" }
Dan Albert8b72aef2015-03-23 19:13:21 -07001371}
Doug Zongker96a57e72010-09-26 14:57:41 -07001372
1373def GetTypeAndDevice(mount_point, info):
1374 fstab = info["fstab"]
1375 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001376 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1377 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001378 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001379 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001380
1381
1382def ParseCertificate(data):
1383 """Parse a PEM-format certificate."""
1384 cert = []
1385 save = False
1386 for line in data.split("\n"):
1387 if "--END CERTIFICATE--" in line:
1388 break
1389 if save:
1390 cert.append(line)
1391 if "--BEGIN CERTIFICATE--" in line:
1392 save = True
1393 cert = "".join(cert).decode('base64')
1394 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001395
Doug Zongker412c02f2014-02-13 10:58:24 -08001396def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1397 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001398 """Generate a binary patch that creates the recovery image starting
1399 with the boot image. (Most of the space in these images is just the
1400 kernel, which is identical for the two, so the resulting patch
1401 should be efficient.) Add it to the output zip, along with a shell
1402 script that is run from init.rc on first boot to actually do the
1403 patching and install the new recovery image.
1404
1405 recovery_img and boot_img should be File objects for the
1406 corresponding images. info should be the dictionary returned by
1407 common.LoadInfoDict() on the input target_files.
1408 """
1409
Doug Zongker412c02f2014-02-13 10:58:24 -08001410 if info_dict is None:
1411 info_dict = OPTIONS.info_dict
1412
Doug Zongkerc9253822014-02-04 12:17:58 -08001413 diff_program = ["imgdiff"]
1414 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1415 if os.path.exists(path):
1416 diff_program.append("-b")
1417 diff_program.append(path)
1418 bonus_args = "-b /system/etc/recovery-resource.dat"
1419 else:
1420 bonus_args = ""
1421
1422 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1423 _, _, patch = d.ComputePatch()
1424 output_sink("recovery-from-boot.p", patch)
1425
Dan Albertebb19aa2015-03-27 19:11:53 -07001426 try:
1427 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1428 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1429 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001430 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001431
1432 sh = """#!/system/bin/sh
Ricardo Cerqueira83197082014-06-19 01:45:15 +01001433if [ -f /system/etc/recovery-transform.sh ]; then
1434 exec sh /system/etc/recovery-transform.sh %(recovery_size)d %(recovery_sha1)s %(boot_size)d %(boot_sha1)s
1435fi
1436
Doug Zongkerc9253822014-02-04 12:17:58 -08001437if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1438 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"
1439else
1440 log -t recovery "Recovery image already installed"
1441fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001442""" % {'boot_size': boot_img.size,
1443 'boot_sha1': boot_img.sha1,
1444 'recovery_size': recovery_img.size,
1445 'recovery_sha1': recovery_img.sha1,
1446 'boot_type': boot_type,
1447 'boot_device': boot_device,
1448 'recovery_type': recovery_type,
1449 'recovery_device': recovery_device,
1450 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001451
1452 # The install script location moved from /system/etc to /system/bin
1453 # in the L release. Parse the init.rc file to find out where the
1454 # target-files expects it to be, and put it there.
1455 sh_location = "etc/install-recovery.sh"
1456 try:
1457 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1458 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001459 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001460 if m:
1461 sh_location = m.group(1)
1462 print "putting script in", sh_location
1463 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001464 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001465 print "failed to read init.rc: %s" % (e,)
1466
1467 output_sink(sh_location, sh)